From a88c38e65fe57f436ed45bfef8dfc840125c6c9f Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 18 May 2024 13:57:39 +0200 Subject: [PATCH 01/16] Draft for KHR_accessor_float64 support --- .../de/javagl/jgltf/impl/v2/Accessor.java | 2 +- .../de/javagl/jgltf/model/AccessorDatas.java | 79 ++++++ .../jgltf/model/AccessorDoubleData.java | 251 ++++++++++++++++++ .../java/de/javagl/jgltf/model/Accessors.java | 3 + .../de/javagl/jgltf/model/GltfConstants.java | 6 +- .../de/javagl/jgltf/model/NumberArrays.java | 16 ++ .../de/javagl/jgltf/model/io/Buffers.java | 18 ++ .../structure/BufferStructureBuilder.java | 27 ++ 8 files changed, 400 insertions(+), 2 deletions(-) create mode 100644 jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorDoubleData.java diff --git a/jgltf-impl-v2/src/main/java/de/javagl/jgltf/impl/v2/Accessor.java b/jgltf-impl-v2/src/main/java/de/javagl/jgltf/impl/v2/Accessor.java index 4d732537..ad33a753 100644 --- a/jgltf-impl-v2/src/main/java/de/javagl/jgltf/impl/v2/Accessor.java +++ b/jgltf-impl-v2/src/main/java/de/javagl/jgltf/impl/v2/Accessor.java @@ -168,7 +168,7 @@ public void setComponentType(Integer componentType) { if (componentType == null) { throw new NullPointerException((("Invalid value for componentType: "+ componentType)+", may not be null")); } - if ((((((componentType!= 5120)&&(componentType!= 5121))&&(componentType!= 5122))&&(componentType!= 5123))&&(componentType!= 5125))&&(componentType!= 5126)) { + if ((((((componentType!= 5120)&&(componentType!= 5121))&&(componentType!= 5122))&&(componentType!= 5123))&&(componentType!= 5125))&&(componentType!= 5126)&&(componentType!= 5130)) { throw new IllegalArgumentException((("Invalid value for componentType: "+ componentType)+", valid: [5120, 5121, 5122, 5123, 5125, 5126]")); } this.componentType = componentType; diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorDatas.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorDatas.java index 23a2266c..12712aa2 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorDatas.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorDatas.java @@ -86,6 +86,10 @@ public static AccessorData create( { return createFloat(accessorModel, byteBuffer); } + if (accessorModel.getComponentDataType() == double.class) + { + return createDouble(accessorModel, byteBuffer); + } // Should never happen logger.severe("Invalid component data type: " + accessorModel.getComponentDataType()); @@ -138,6 +142,12 @@ public static AccessorData create( componentType, bufferViewData, byteOffset, count, elementType, byteStride); } + if (isDoubleType(componentType)) + { + return new AccessorDoubleData( + componentType, bufferViewData, byteOffset, count, + elementType, byteStride); + } throw new IllegalArgumentException( "Not a valid component type: " + componentType); } @@ -197,6 +207,17 @@ public static boolean isFloatType(int type) return type == GltfConstants.GL_FLOAT; } + /** + * Returns whether the given constant is GL_DOUBLE. + * + * @param type The type constant + * @return Whether the type is a double type + */ + public static boolean isDoubleType(int type) + { + return type == GltfConstants.GL_DOUBLE; + } + /** * Returns whether the given constant is GL_UNSIGNED_BYTE, * GL_UNSIGNED_SHORT or GL_UNSIGNED_INT. @@ -288,6 +309,24 @@ static void validateFloatType(int type) } } + /** + * Make sure that the given type is GL_DOUBLE, and throw an + * IllegalArgumentException if this is not the case. + * + * @param type The type constant + * @throws IllegalArgumentException If the given type is not + * GL_DOUBLE + */ + static void validateDoubleType(int type) + { + if (!isDoubleType(type)) + { + throw new IllegalArgumentException( + "The type is not GL_DOUBLE, but " + + GltfConstants.stringFor(type)); + } + } + /** @@ -432,6 +471,8 @@ public static AccessorFloatData createFloat(AccessorModel accessorModel) * @return The {@link AccessorFloatData} * @throws NullPointerException If any argument is null * @throws IllegalArgumentException If the + * {@link AccessorModel#getComponentType() component type} of the given + * accessorModel is not GL_FLOAT */ private static AccessorFloatData createFloat( AccessorModel accessorModel, ByteBuffer bufferViewByteBuffer) @@ -444,6 +485,29 @@ private static AccessorFloatData createFloat( accessorModel.getByteStride()); } + /** + * Creates an {@link AccessorDoubleData} for the given {@link AccessorModel} + * + * @param accessorModel The {@link AccessorModel} + * @param bufferViewByteBuffer The byte buffer of the + * {@link BufferViewModel} referenced by the {@link AccessorModel} + * @return The {@link AccessorDoubleData} + * @throws NullPointerException If any argument is null + * @throws IllegalArgumentException If the + * {@link AccessorModel#getComponentType() component type} of the given + * accessorModel is not GL_DOUBLE + */ + private static AccessorDoubleData createDouble( + AccessorModel accessorModel, ByteBuffer bufferViewByteBuffer) + { + return new AccessorDoubleData(accessorModel.getComponentType(), + bufferViewByteBuffer, + accessorModel.getByteOffset(), + accessorModel.getCount(), + accessorModel.getElementType(), + accessorModel.getByteStride()); + } + /** * Validate that the given {@link AccessorModel} parameters are valid for * accessing a buffer with the given capacity @@ -512,6 +576,13 @@ public static Number[] computeMin(AccessorData accessorData) return NumberArrays.asNumbers( accessorFloatData.computeMin()); } + if (accessorData instanceof AccessorDoubleData) + { + AccessorDoubleData accessorDoubleData = + (AccessorDoubleData) accessorData; + return NumberArrays.asNumbers( + accessorDoubleData.computeMin()); + } throw new IllegalArgumentException( "Invalid data type: " + accessorData); } @@ -554,6 +625,13 @@ public static Number[] computeMax(AccessorData accessorData) return NumberArrays.asNumbers( accessorFloatData.computeMax()); } + if (accessorData instanceof AccessorDoubleData) + { + AccessorDoubleData accessorDoubleData = + (AccessorDoubleData) accessorData; + return NumberArrays.asNumbers( + accessorDoubleData.computeMax()); + } throw new IllegalArgumentException( "Invalid data type: " + accessorData); } @@ -565,6 +643,7 @@ public static Number[] computeMax(AccessorData accessorData) * {@link AccessorShortData#createString(Locale, String, int)}, * {@link AccessorIntData#createString(Locale, String, int)} or * {@link AccessorFloatData#createString(Locale, String, int)}, + * {@link AccessorDoubleData#createString(Locale, String, int)}, * depending on the type of the given data, with an unspecified * format string. * diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorDoubleData.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorDoubleData.java new file mode 100644 index 00000000..57af57ae --- /dev/null +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorDoubleData.java @@ -0,0 +1,251 @@ +/* + * www.javagl.de - JglTF + * + * Copyright 2015-2016 Marco Hutter - http://www.javagl.de + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +package de.javagl.jgltf.model; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Locale; + +/** + * A class for accessing the data that is described by an accessor. + * It allows accessing the byte buffer of the buffer view of the + * accessor, depending on the accessor parameters.
+ *
+ * This data consists of several elements (for example, 3D double vectors), + * which consist of several components (for example, the 3 double values). + */ +public final class AccessorDoubleData + extends AbstractAccessorData + implements AccessorData +{ + /** + * Creates a new instance for accessing the data in the given + * byte buffer, according to the rules described by the given + * accessor parameters. + * @param componentType The component type + * @param bufferViewByteBuffer The byte buffer of the buffer view + * @param byteOffset The byte offset in the buffer view + * @param numElements The number of elements + * @param elementType The {@link ElementType} + * @param byteStride The byte stride between two elements. If this + * is null or 0, then the stride will + * be the size of one element. + * + * @throws NullPointerException If the bufferViewByteBuffer is + * null + * @throws IllegalArgumentException If the component type is not + * GL_DOUBLE + * @throws IllegalArgumentException If the given byte buffer does not + * have a sufficient capacity to provide the data for the accessor + */ + public AccessorDoubleData(int componentType, + ByteBuffer bufferViewByteBuffer, int byteOffset, int numElements, + ElementType elementType, Integer byteStride) + { + super(componentType, double.class, bufferViewByteBuffer, byteOffset, + numElements, elementType, Double.BYTES, byteStride); + AccessorDatas.validateDoubleType(componentType); + + int numBytesPerElement = + getNumComponentsPerElement() * getNumBytesPerComponent(); + AccessorDatas.validateCapacity(byteOffset, getNumElements(), + numBytesPerElement, getByteStridePerElement(), + bufferViewByteBuffer.capacity()); + } + + /** + * Returns the value of the specified component of the specified element + * + * @param elementIndex The element index + * @param componentIndex The component index + * @return The value + * @throws IndexOutOfBoundsException If the given indices cause the + * underlying buffer to be accessed out of bounds + */ + public double get(int elementIndex, int componentIndex) + { + int byteIndex = getByteIndex(elementIndex, componentIndex); + return getBufferViewByteBuffer().getDouble(byteIndex); + } + + /** + * Returns the value of the specified component + * + * @param globalComponentIndex The global component index + * @return The value + * @throws IndexOutOfBoundsException If the given index causes the + * underlying buffer to be accessed out of bounds + */ + public double get(int globalComponentIndex) + { + int elementIndex = + globalComponentIndex / getNumComponentsPerElement(); + int componentIndex = + globalComponentIndex % getNumComponentsPerElement(); + return get(elementIndex, componentIndex); + } + + /** + * Set the value of the specified component of the specified element + * + * @param elementIndex The element index + * @param componentIndex The component index + * @param value The value + * @throws IndexOutOfBoundsException If the given indices cause the + * underlying buffer to be accessed out of bounds + */ + public void set(int elementIndex, int componentIndex, double value) + { + int byteIndex = getByteIndex(elementIndex, componentIndex); + getBufferViewByteBuffer().putDouble(byteIndex, value); + } + + /** + * Set the value of the specified component + * + * @param globalComponentIndex The global component index + * @param value The value + * @throws IndexOutOfBoundsException If the given index causes the + * underlying buffer to be accessed out of bounds + */ + public void set(int globalComponentIndex, double value) + { + int elementIndex = + globalComponentIndex / getNumComponentsPerElement(); + int componentIndex = + globalComponentIndex % getNumComponentsPerElement(); + set(elementIndex, componentIndex, value); + } + + + /** + * Returns an array containing the minimum component values of all elements + * of this accessor data. This will be an array whose length is the + * {@link #getNumComponentsPerElement() number of components per element}. + * + * @return The minimum values + */ + public double[] computeMin() + { + double result[] = new double[getNumComponentsPerElement()]; + Arrays.fill(result, Double.MAX_VALUE); + for (int e = 0; e < getNumElements(); e++) + { + for (int c = 0; c < getNumComponentsPerElement(); c++) + { + result[c] = Math.min(result[c], get(e, c)); + } + } + return result; + } + + /** + * Returns an array containing the maximum component values of all elements + * of this accessor data. This will be an array whose length is the + * {@link #getNumComponentsPerElement() number of components per element}. + * + * @return The minimum values + */ + public double[] computeMax() + { + double result[] = new double[getNumComponentsPerElement()]; + Arrays.fill(result, -Double.MAX_VALUE); + for (int e = 0; e < getNumElements(); e++) + { + for (int c = 0; c < getNumComponentsPerElement(); c++) + { + result[c] = Math.max(result[c], get(e, c)); + } + } + return result; + } + + @Override + public ByteBuffer createByteBuffer() + { + int totalNumComponents = getTotalNumComponents(); + int totalBytes = totalNumComponents * getNumBytesPerComponent(); + ByteBuffer result = ByteBuffer.allocateDirect(totalBytes) + .order(ByteOrder.nativeOrder()); + for (int i=0; i 0) + { + sb.append(", "); + if (elementsPerRow > 0 && (e % elementsPerRow) == 0) + { + sb.append("\n "); + } + } + if (nc > 1) + { + sb.append("("); + } + for (int c = 0; c < nc; c++) + { + if (c > 0) + { + sb.append(", "); + } + double component = get(e, c); + sb.append(String.format(locale, format, component)); + } + if (nc > 1) + { + sb.append(")"); + } + } + sb.append("]"); + return sb.toString(); + } + +} \ No newline at end of file diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/Accessors.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/Accessors.java index c5bfd6ce..78abd718 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/Accessors.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/Accessors.java @@ -100,6 +100,7 @@ public static int getNumBytesForAccessorComponentType(int componentType) case GltfConstants.GL_INT: return 4; case GltfConstants.GL_UNSIGNED_INT: return 4; case GltfConstants.GL_FLOAT: return 4; + case GltfConstants.GL_DOUBLE: return 8; default: break; } @@ -118,6 +119,7 @@ public static int getNumBytesForAccessorComponentType(int componentType) * GL_INT : int.class * GL_UNSIGNED_INT : int.class * GL_FLOAT : float.class + * GL_DOUBLE : double.class * * * @param componentType The component type @@ -137,6 +139,7 @@ public static Class getDataTypeForAccessorComponentType( case GltfConstants.GL_INT: return int.class; case GltfConstants.GL_UNSIGNED_INT: return int.class; case GltfConstants.GL_FLOAT: return float.class; + case GltfConstants.GL_DOUBLE: return double.class; default: break; } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/GltfConstants.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/GltfConstants.java index 45e6fc52..5677b31c 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/GltfConstants.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/GltfConstants.java @@ -89,7 +89,10 @@ public class GltfConstants */ public static final int GL_FLOAT = 5126; - + /** + * The GL_DOUBLE constant (5130) + */ + public static final int GL_DOUBLE = 5130; /** * The GL_FLOAT_VEC2 constant (35664) @@ -510,6 +513,7 @@ public static String stringFor(int constant) case GL_INT : return "GL_INT"; case GL_UNSIGNED_INT : return "GL_UNSIGNED_INT"; case GL_FLOAT : return "GL_FLOAT"; + case GL_DOUBLE : return "GL_DOUBLE"; case GL_FLOAT_VEC2 : return "GL_FLOAT_VEC2"; case GL_FLOAT_VEC3 : return "GL_FLOAT_VEC3"; diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/NumberArrays.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/NumberArrays.java index 71bbde14..736ddb1a 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/NumberArrays.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/NumberArrays.java @@ -79,6 +79,22 @@ static Number[] asNumbers(float array[]) return result; } + /** + * Convert the given array into a Number array + * + * @param array The array + * @return The result + */ + static Number[] asNumbers(double array[]) + { + Number result[] = new Number[array.length]; + for (int i = 0; i < array.length; i++) + { + result[i] = array[i]; + } + return result; + } + /** * Private constructor to prevent instantiation diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/io/Buffers.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/io/Buffers.java index 16fc263d..69b45390 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/io/Buffers.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/io/Buffers.java @@ -29,6 +29,7 @@ import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.DoubleBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; @@ -229,6 +230,23 @@ public static ByteBuffer createByteBufferFrom(FloatBuffer buffer) return byteBuffer; } + /** + * Create a new direct byte buffer with native byte order that has the + * same contents as the given double buffer. + * + * @param buffer The input buffer + * @return The new byte buffer + */ + public static ByteBuffer createByteBufferFrom(DoubleBuffer buffer) + { + ByteBuffer byteBuffer = + ByteBuffer.allocateDirect(buffer.capacity() * Double.BYTES); + DoubleBuffer doubleBuffer = + byteBuffer.order(ByteOrder.nativeOrder()).asDoubleBuffer(); + doubleBuffer.put(buffer.slice()); + return byteBuffer; + } + /** * Create a new direct byte buffer with native byte order that has the * same contents as the given int buffer. diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureBuilder.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureBuilder.java index d68b3241..8fd8fc2b 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureBuilder.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureBuilder.java @@ -27,6 +27,7 @@ package de.javagl.jgltf.model.structure; import java.nio.ByteBuffer; +import java.nio.DoubleBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; @@ -196,6 +197,32 @@ public AccessorModel createAccessorModel( return createAccessorModel(idPrefix, componentType, type, byteBuffer); } + /** + * Create an {@link AccessorModel} in the {@link BufferStructure} that + * is currently being built. + * + * @param idPrefix The ID prefix of the {@link AccessorModel} + * @param data The actual data + * @param type The type of the data, as a string corresponding to + * the {@link ElementType} of the accessor + * @return The {@link AccessorModel} + */ + public AccessorModel createAccessorModel( + String idPrefix, double data[], String type) + { + ElementType elementType = ElementType.valueOf(type); + int numComponents = elementType.getNumComponents(); + if (data.length % numComponents != 0) + { + throw new IllegalArgumentException("Invalid data for type " + type + + ". The data.length is not divisble by " + numComponents); + } + int componentType = GltfConstants.GL_DOUBLE; + ByteBuffer byteBuffer = + Buffers.createByteBufferFrom(DoubleBuffer.wrap(data)); + return createAccessorModel(idPrefix, componentType, type, byteBuffer); + } + /** * Create an {@link AccessorModel} in the {@link BufferStructure} that * is currently being built, using the component type From 162169a6c92b6a87ea821d0e2a886b021ccbf257 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Thu, 23 Oct 2025 14:48:53 +0200 Subject: [PATCH 02/16] Omit empty padding buffers --- .../model/structure/BufferStructureBuilder.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureBuilder.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureBuilder.java index d68b3241..0c2b192b 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureBuilder.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureBuilder.java @@ -569,7 +569,10 @@ private void processBufferModel(DefaultBufferModel bufferModel) bufferStructure.addPaddingByteIndices( bufferModel, accumulatedBufferBytes, paddingBytesForBuffer); accumulatedBufferBytes += paddingBytesForBuffer; - bufferElements.add(ByteBuffer.allocate(paddingBytesForBuffer)); + if (paddingBytesForBuffer > 0) + { + bufferElements.add(ByteBuffer.allocate(paddingBytesForBuffer)); + } bufferViewModel.setByteOffset(accumulatedBufferBytes); @@ -669,8 +672,11 @@ private void processBufferModel(DefaultBufferModel bufferModel) accumulatedBufferViewBytes += accessorByteBuffer.capacity(); accumulatedBufferBytes += paddingBytesForBufferView; - bufferElements.add( - ByteBuffer.allocate(paddingBytesForBufferView)); + if (paddingBytesForBufferView > 0) + { + bufferElements.add( + ByteBuffer.allocate(paddingBytesForBufferView)); + } accumulatedBufferBytes += accessorByteBuffer.capacity(); bufferElements.add(accessorByteBuffer); From 691b96d7a7874c89988644afc7e4f44013da106a Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Thu, 23 Oct 2025 20:39:23 +0200 Subject: [PATCH 03/16] Prepare support for 16 bit floats --- .../src/main/java/de/javagl/jgltf/impl/v2/Accessor.java | 4 ++-- .../src/main/java/de/javagl/jgltf/model/AccessorData.java | 2 ++ .../main/java/de/javagl/jgltf/model/AccessorDatas.java | 8 +++++++- .../src/main/java/de/javagl/jgltf/model/Accessors.java | 3 +++ .../main/java/de/javagl/jgltf/model/GltfConstants.java | 5 +++++ 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/jgltf-impl-v2/src/main/java/de/javagl/jgltf/impl/v2/Accessor.java b/jgltf-impl-v2/src/main/java/de/javagl/jgltf/impl/v2/Accessor.java index ad33a753..cf33ff27 100644 --- a/jgltf-impl-v2/src/main/java/de/javagl/jgltf/impl/v2/Accessor.java +++ b/jgltf-impl-v2/src/main/java/de/javagl/jgltf/impl/v2/Accessor.java @@ -168,8 +168,8 @@ public void setComponentType(Integer componentType) { if (componentType == null) { throw new NullPointerException((("Invalid value for componentType: "+ componentType)+", may not be null")); } - if ((((((componentType!= 5120)&&(componentType!= 5121))&&(componentType!= 5122))&&(componentType!= 5123))&&(componentType!= 5125))&&(componentType!= 5126)&&(componentType!= 5130)) { - throw new IllegalArgumentException((("Invalid value for componentType: "+ componentType)+", valid: [5120, 5121, 5122, 5123, 5125, 5126]")); + if ((((((componentType!= 5120)&&(componentType!= 5121))&&(componentType!= 5122))&&(componentType!= 5123))&&(componentType!= 5125))&&(componentType!= 5126)&&(componentType!= 5130)&&(componentType!= 5131)) { + throw new IllegalArgumentException((("Invalid value for componentType: "+ componentType)+", valid: [5120, 5121, 5122, 5123, 5125, 5126, 5130, 5131]")); } this.componentType = componentType; } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorData.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorData.java index cc2fef4e..d97c7596 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorData.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorData.java @@ -41,6 +41,8 @@ * {@link AccessorIntData} *
  • For float.class, the implementation is an * {@link AccessorFloatData}
  • + *
  • For double.class, the implementation is an + * {@link AccessorDoubleData}
  • * */ public interface AccessorData diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorDatas.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorDatas.java index 12712aa2..ace38b29 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorDatas.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorDatas.java @@ -172,6 +172,11 @@ public static boolean isByteType(int type) * Returns whether the given constant is GL_SHORT or * GL_UNSIGNED_SHORT. * + * Given that there is no actual native representation of + * "half-precision (16 bit)" floating point values in Java, + * this also returns true when the given + * constant is GL_HALF. + * * @param type The type constant * @return Whether the type is a short type */ @@ -179,7 +184,8 @@ public static boolean isShortType(int type) { return type == GltfConstants.GL_SHORT || - type == GltfConstants.GL_UNSIGNED_SHORT; + type == GltfConstants.GL_UNSIGNED_SHORT || + type == GltfConstants.GL_HALF; } /** diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/Accessors.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/Accessors.java index 78abd718..b2a6d3d0 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/Accessors.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/Accessors.java @@ -96,6 +96,7 @@ public static int getNumBytesForAccessorComponentType(int componentType) case GltfConstants.GL_BYTE: return 1; case GltfConstants.GL_UNSIGNED_BYTE: return 1; case GltfConstants.GL_SHORT: return 2; + case GltfConstants.GL_HALF: return 2; case GltfConstants.GL_UNSIGNED_SHORT: return 2; case GltfConstants.GL_INT: return 4; case GltfConstants.GL_UNSIGNED_INT: return 4; @@ -116,6 +117,7 @@ public static int getNumBytesForAccessorComponentType(int componentType) * GL_UNSIGNED_BYTE : byte.class * GL_SHORT : short.class * GL_UNSIGNED_SHORT : short.class + * GL_HALF : short.class * GL_INT : int.class * GL_UNSIGNED_INT : int.class * GL_FLOAT : float.class @@ -136,6 +138,7 @@ public static Class getDataTypeForAccessorComponentType( case GltfConstants.GL_UNSIGNED_BYTE: return byte.class; case GltfConstants.GL_SHORT: return short.class; case GltfConstants.GL_UNSIGNED_SHORT: return short.class; + case GltfConstants.GL_HALF: return short.class; case GltfConstants.GL_INT: return int.class; case GltfConstants.GL_UNSIGNED_INT: return int.class; case GltfConstants.GL_FLOAT: return float.class; diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/GltfConstants.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/GltfConstants.java index 5677b31c..c73d555b 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/GltfConstants.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/GltfConstants.java @@ -93,6 +93,11 @@ public class GltfConstants * The GL_DOUBLE constant (5130) */ public static final int GL_DOUBLE = 5130; + + /** + * The GL_HALF constant (5131) + */ + public static final int GL_HALF = 5131; /** * The GL_FLOAT_VEC2 constant (35664) From ff85db1bd3fd52af358763ebbb9e1a7a4b331fdb Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Fri, 24 Oct 2025 15:52:05 +0200 Subject: [PATCH 04/16] Add string creation for AccessorDoubleData --- .../main/java/de/javagl/jgltf/model/AccessorDatas.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorDatas.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorDatas.java index ace38b29..c7f95f37 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorDatas.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/AccessorDatas.java @@ -696,6 +696,15 @@ public static String createString( Locale.ENGLISH, "%10.5f", elementsPerRow); return accessorDataString; } + if (accessorData instanceof AccessorDoubleData) + { + AccessorDoubleData accessorDoubleData = + (AccessorDoubleData) accessorData; + String accessorDataString = + accessorDoubleData.createString( + Locale.ENGLISH, "%10.5f", elementsPerRow); + return accessorDataString; + } return "Unknown accessor data type: " + accessorData; } From 4d9d63301727bfd5bc5a81720ff0d5d52c010eea Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 25 Oct 2025 00:35:27 +0200 Subject: [PATCH 05/16] Use proper byte order for fallback buffer. That took me about 20 hours. It's the little things. --- .../main/java/de/javagl/jgltf/model/v2/GltfModelCreatorV2.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/v2/GltfModelCreatorV2.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/v2/GltfModelCreatorV2.java index 33cfdfed..37174011 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/v2/GltfModelCreatorV2.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/v2/GltfModelCreatorV2.java @@ -857,7 +857,7 @@ private void initBufferModels() + "a uri. Binary chunks that are not the main GLB " + "buffer are not supported."); ByteBuffer fallbackBuffer = - ByteBuffer.allocate(buffer.getByteLength()); + Buffers.create(buffer.getByteLength()); bufferModel.setBufferData(fallbackBuffer); } else From e8dd82ab6e068ed8d3c485d529b51476ffaf9b3b Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 25 Oct 2025 14:20:48 +0200 Subject: [PATCH 06/16] Cleaner handling of meshopt fallback buffer --- .../jgltf/model/v2/GltfModelCreatorV2.java | 105 ++++++++++++++---- 1 file changed, 81 insertions(+), 24 deletions(-) diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/v2/GltfModelCreatorV2.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/v2/GltfModelCreatorV2.java index 37174011..bdf87215 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/v2/GltfModelCreatorV2.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/v2/GltfModelCreatorV2.java @@ -842,34 +842,91 @@ private void initBufferModels() } else { - String uri = buffer.getUri(); - if (IO.isDataUriString(uri)) - { - byte data[] = IO.readDataUri(uri); - ByteBuffer bufferData = Buffers.create(data); - bufferModel.setBufferData(bufferData); - } - else - { - if (uri == null) - { - logger.warning("Buffer " + i + " does not have " - + "a uri. Binary chunks that are not the main GLB " - + "buffer are not supported."); - ByteBuffer fallbackBuffer = - Buffers.create(buffer.getByteLength()); - bufferModel.setBufferData(fallbackBuffer); - } - else - { - ByteBuffer bufferData = gltfAsset.getReferenceData(uri); - bufferModel.setBufferData(bufferData); - } - } + initBufferData(i, buffer, bufferModel); } } } + + /** + * Initialize the buffer data of the given buffer model, based on the + * information from the given buffer. + * + * @param index The index of the buffer + * @param buffer The buffer + * @param bufferModel The buffer model + */ + private void initBufferData(int index, Buffer buffer, + DefaultBufferModel bufferModel) + { + String uri = buffer.getUri(); + + // When the URI is a data URI, decode it directly and assign + // the result as the buffer data + if (IO.isDataUriString(uri)) + { + byte data[] = IO.readDataUri(uri); + ByteBuffer bufferData = Buffers.create(data); + bufferModel.setBufferData(bufferData); + return; + } + + // For any other URI, the data is resolved as a reference data + // from the glTF asset + if (uri != null) + { + ByteBuffer bufferData = gltfAsset.getReferenceData(uri); + bufferModel.setBufferData(bufferData); + return; + } + + // Found a buffer without a URI. + // Special handling for the meshopt fallback buffer + if (isMeshoptFallbackBuffer(buffer)) + { + ByteBuffer fallbackBuffer = Buffers.create(buffer.getByteLength()); + bufferModel.setBufferData(fallbackBuffer); + return; + } + + logger.warning("Buffer " + index + " does not have " + + "a uri. Binary chunks that are not the main " + + "GLB buffer are not supported."); + } + /** + * Returns whether the given buffer is a meshopt fallback buffer. + * + * This means that it has a EXT_meshopt_compression + * extension object that defines fallback: true. + * + * @param buffer The buffer + * @return Whether the buffer is a fallback buffer. + */ + private static boolean isMeshoptFallbackBuffer(Buffer buffer) + { + Map extensions = buffer.getExtensions(); + if (extensions == null) + { + return false; + } + Object extensionObject = extensions.get("EXT_meshopt_compression"); + if (extensionObject == null) + { + return false; + } + if (!(extensionObject instanceof Map)) + { + return false; + } + Map extensionObjectMap = (Map) extensionObject; + Object fallbackObject = extensionObjectMap.get("fallback"); + if (fallbackObject == null) + { + return false; + } + boolean isFallback = Boolean.TRUE.equals(fallbackObject); + return isFallback; + } /** * Initialize the {@link BufferViewModel} instances From 9224b2212f5b7b16a4db17e53abf70e78c2582b3 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 25 Oct 2025 23:45:06 +0200 Subject: [PATCH 07/16] Update spec files for omitted default sampler --- .../unitCubeTextured.glb | Bin 5480 -> 5424 bytes .../unitCubeTextured.gltf | 5 ----- .../golden-glTF-to-glTF/unitCubeTextured.gltf | 5 ----- 3 files changed, 10 deletions(-) diff --git a/jgltf-model/src/test/resources/testModels/v2/unitCubeTextured/golden-glTF-to-glTF-Binary/unitCubeTextured.glb b/jgltf-model/src/test/resources/testModels/v2/unitCubeTextured/golden-glTF-to-glTF-Binary/unitCubeTextured.glb index a12fccc1467d376d0b40242c2773b28e648d2ca0..506f9dec5aeb09e4c9e46cf6807336e1b1ba6b46 100644 GIT binary patch delta 44 zcmaE%wLyz7JtxGCiGhK^K$L;u49iBosVtMvu^4dYmlh?bDp?uS#@22YV+|4j02?t3 Ar~m)} delta 96 zcmdm>^+Jm;JtxGCiGhJ3LzIDmg>@s}R2H?w+=86cqGBbh=xU|%qQrt=B`ZS%6H9X) mFfRnkn>>%jR05(@$;v=SsW`v1C^;3#tc|Tz*nE#ANB{sDZX8Jf diff --git a/jgltf-model/src/test/resources/testModels/v2/unitCubeTextured/golden-glTF-to-glTF-Embedded/unitCubeTextured.gltf b/jgltf-model/src/test/resources/testModels/v2/unitCubeTextured/golden-glTF-to-glTF-Embedded/unitCubeTextured.gltf index 116f79e6..e9e15c74 100644 --- a/jgltf-model/src/test/resources/testModels/v2/unitCubeTextured/golden-glTF-to-glTF-Embedded/unitCubeTextured.gltf +++ b/jgltf-model/src/test/resources/testModels/v2/unitCubeTextured/golden-glTF-to-glTF-Embedded/unitCubeTextured.gltf @@ -92,16 +92,11 @@ "nodes" : [ { "mesh" : 0 } ], - "samplers" : [ { - "wrapS" : 10497, - "wrapT" : 10497 - } ], "scene" : 0, "scenes" : [ { "nodes" : [ 0 ] } ], "textures" : [ { - "sampler" : 0, "source" : 0 } ] } \ No newline at end of file diff --git a/jgltf-model/src/test/resources/testModels/v2/unitCubeTextured/golden-glTF-to-glTF/unitCubeTextured.gltf b/jgltf-model/src/test/resources/testModels/v2/unitCubeTextured/golden-glTF-to-glTF/unitCubeTextured.gltf index a09a8625..ea4b29f7 100644 --- a/jgltf-model/src/test/resources/testModels/v2/unitCubeTextured/golden-glTF-to-glTF/unitCubeTextured.gltf +++ b/jgltf-model/src/test/resources/testModels/v2/unitCubeTextured/golden-glTF-to-glTF/unitCubeTextured.gltf @@ -92,16 +92,11 @@ "nodes" : [ { "mesh" : 0 } ], - "samplers" : [ { - "wrapS" : 10497, - "wrapT" : 10497 - } ], "scene" : 0, "scenes" : [ { "nodes" : [ 0 ] } ], "textures" : [ { - "sampler" : 0, "source" : 0 } ] } \ No newline at end of file From 9ad08d0e6a3893fde57836efc06b93d1e69a4f5e Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 25 Oct 2025 23:49:55 +0200 Subject: [PATCH 08/16] Extract buffer structure processing to own class --- .../structure/BufferStructureBuilder.java | 367 +------------- .../structure/BufferStructureProcessor.java | 455 ++++++++++++++++++ 2 files changed, 459 insertions(+), 363 deletions(-) create mode 100644 jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureProcessor.java diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureBuilder.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureBuilder.java index b4e2ec14..60faaa9e 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureBuilder.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureBuilder.java @@ -37,9 +37,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.logging.Logger; -import de.javagl.jgltf.model.AccessorData; import de.javagl.jgltf.model.AccessorDatas; import de.javagl.jgltf.model.AccessorModel; import de.javagl.jgltf.model.Accessors; @@ -69,12 +67,6 @@ */ public final class BufferStructureBuilder { - /** - * The logger used in this class - */ - private static final Logger logger = - Logger.getLogger(BufferStructureBuilder.class.getName()); - /** * The {@link BufferStructure} that is created by this instance */ @@ -526,362 +518,11 @@ public BufferStructure build() + "The 'createBufferModel' method must be called before " + "building the buffer structure"); } - buildDefault(); + BufferStructureProcessor bufferStructureProcessor = + new BufferStructureProcessor( + bufferStructure, imageBufferViewDataMap); + bufferStructureProcessor.process(); return bufferStructure; } - /** - * Internal method to finalize the elements of the current - * {@link BufferStructure}: This will compute the byte offsets and - * paddings, and create the actual {@link BufferModel#getBufferData() - * buffer data}. This does a default construction. A method for - * interleaved construction may be added later, in some form. - */ - private void buildDefault() - { - List bufferModels = - bufferStructure.getBufferModels(); - for (DefaultBufferModel bufferModel : bufferModels) - { - processBufferModel(bufferModel); - } - } - - /** - * Process the given {@link BufferModel} during the build process. - * This will compute the properties of the model, as well as the - * properties of its {@link AccessorModel} and {@link BufferViewModel} - * instances, that can only be determined when the buffer is about - * to be finalized (e.g. the byte stride, byte offsets and lengths). - * - * @param bufferModel The {@link BufferModel} - */ - private void processBufferModel(DefaultBufferModel bufferModel) - { - // Compute the mapping from AccessorModel instances to the - // byte buffers that contain their data, as given when they have - // been added. These byte buffers may have to be combined, reordered - // or padded in order to create the actual buffer data - Map rawAccessorModelByteBuffers = - new LinkedHashMap(); - for (DefaultAccessorModel accessorModel : - bufferStructure.getAccessorModels()) - { - AccessorData accessorData = accessorModel.getAccessorData(); - ByteBuffer byteBuffer = accessorData.createByteBuffer(); - rawAccessorModelByteBuffers.put(accessorModel, byteBuffer); - } - - // The sequence of accessor data buffers and paddings that - // eventually will be combined to create the buffer data - List bufferElements = new ArrayList(); - - List bufferViewModels = - bufferStructure.getBufferViewModels(bufferModel); - - int accumulatedBufferBytes = 0; - for (DefaultBufferViewModel bufferViewModel : bufferViewModels) - { - List accessorModels = - bufferStructure.getAccessorModels(bufferViewModel); - - // Handle the padding that may have to be inserted into - // the buffer, before the start of the buffer view: - // The buffer view has to be aligned to the least - // common multiple of the component size of all accessors - int bufferViewAlignmnentBytes = - Alignment.computeAlignmentBytes(accessorModels); - int paddingBytesForBuffer = Alignment.computePadding( - accumulatedBufferBytes, bufferViewAlignmnentBytes); - bufferStructure.addPaddingByteIndices( - bufferModel, accumulatedBufferBytes, paddingBytesForBuffer); - accumulatedBufferBytes += paddingBytesForBuffer; - if (paddingBytesForBuffer > 0) - { - bufferElements.add(ByteBuffer.allocate(paddingBytesForBuffer)); - } - - bufferViewModel.setByteOffset(accumulatedBufferBytes); - - Integer commonByteStride = null; - Integer target = bufferViewModel.getTarget(); - - // When the target is a vertex attribute, then the elements - // of the accessor must be aligned to a multiple of 4. - boolean targetIsVertexAttribute = Objects.equals( - GltfConstants.GL_ARRAY_BUFFER, target); - if (targetIsVertexAttribute) - { - commonByteStride = - Alignment.computeCommonVertexAttributeByteStride( - accessorModels); - for (DefaultAccessorModel accessorModel : accessorModels) - { - int oldByteStride = accessorModel.getByteStride(); - if (oldByteStride != commonByteStride) - { - accessorModel.setByteStride(commonByteStride); - bufferViewModel.setByteStride(commonByteStride); - } - } - - // When there are multiple vertex attribute accessors that refer - // to the same buffer view, then the byte stride must be defined - if (accessorModels.size() > 1) - { - bufferViewModel.setByteStride(commonByteStride); - } - } - - // Handle buffer view models that do not have associated - // accessors, and are only used for images - if (accessorModels.isEmpty()) - { - ByteBuffer bufferViewData = - imageBufferViewDataMap.get(bufferViewModel); - bufferViewModel.setByteLength(bufferViewData.capacity()); - accumulatedBufferBytes += bufferViewData.capacity(); - bufferElements.add(bufferViewData); - } - else - { - int accumulatedBufferViewBytes = 0; - for (DefaultAccessorModel accessorModel : accessorModels) - { - // Handle the padding that may have to be inserted - // into the buffer view before the start of the - // accessor: - // The accessor has to be aligned to its component size. - int accessorAlignmentBytes = - Alignment.computeAlignmentBytes(accessorModel); - int paddingBytesForBufferView = Alignment.computePadding( - accumulatedBufferViewBytes, accessorAlignmentBytes); - if (paddingBytesForBufferView != 0) - { - // Note: The padding here should always be 0, - // because the buffer view was already padded - logger.warning("Inserting " + paddingBytesForBufferView - + " padding bytes for buffer view, due to accessor " - + accessorModel); - } - bufferStructure.addPaddingByteIndices( - bufferModel, accumulatedBufferBytes, - paddingBytesForBufferView); - accumulatedBufferViewBytes += paddingBytesForBufferView; - - accessorModel.setByteOffset( - accumulatedBufferViewBytes); - - int targetByteStride = - accessorModel.getPaddedElementSizeInBytes(); - if (commonByteStride == null) - { - accessorModel.setByteStride(targetByteStride); - } - else - { - targetByteStride = commonByteStride; - } - - // Compute the byte buffer for the accessor data. This - // may have to be restructured by inserting padding bytes - ByteBuffer rawAccessorByteBuffer = - rawAccessorModelByteBuffers.get(accessorModel); - ByteBuffer accessorByteBuffer = rawAccessorByteBuffer; - - int count = accessorModel.getCount(); - ElementType elementType = accessorModel.getElementType(); - int componentType = accessorModel.getComponentType(); - accessorByteBuffer = applyPadding( - rawAccessorByteBuffer, count, elementType, - componentType, targetByteStride); - - accumulatedBufferViewBytes += accessorByteBuffer.capacity(); - - accumulatedBufferBytes += paddingBytesForBufferView; - if (paddingBytesForBufferView > 0) - { - bufferElements.add( - ByteBuffer.allocate(paddingBytesForBufferView)); - } - accumulatedBufferBytes += accessorByteBuffer.capacity(); - bufferElements.add(accessorByteBuffer); - - } - bufferViewModel.setByteLength( - accumulatedBufferViewBytes); - } - } - - validatePadding(bufferModel); - - // Create the buffer data, and assign it to the buffer - ByteBuffer bufferData = Buffers.concat(bufferElements); - bufferModel.setBufferData(bufferData); - } - - /** - * Read the data from the byte buffer that was created with - * {@link AccessorData#createByteBuffer()} and that contains - * the data in packed form, and write it into a new buffer, - * with the padding bytes that are required according to the - * specification. - * - * @param packedByteBuffer The packed byte buffer - * @param count The number of elements in the accessor - * @param elementType The accessor element type - * @param componentType The component type - * @param byteStride The target byte stride (that must at least - * be equal to the {@link ElementType#getByteStride(int)}, but may be - * larger) - * @return The buffer, with the padding applied - */ - private static ByteBuffer applyPadding( - ByteBuffer packedByteBuffer, int count, ElementType elementType, - int componentType, int byteStride) - { - ByteBuffer newByteBuffer = ByteBuffer.allocate(count * byteStride); - int numComponents = elementType.getNumComponents(); - int numBytesPerComponent = - Accessors.getNumBytesForAccessorComponentType(componentType); - int sourceIndex = 0; - int targetIndex = 0; - int padddingForByteStride = - byteStride - elementType.getByteStride(componentType); - for (int i = 0; i < count; i++) - { - for (int c = 0; c < numComponents; c++) - { - for (int b = 0; b < numBytesPerComponent; b++) - { - byte value = packedByteBuffer.get(sourceIndex); - newByteBuffer.put(targetIndex, value); - sourceIndex++; - targetIndex++; - } - int padding = computePaddingBytesAfterComponent(elementType, - componentType, c); - targetIndex += padding; - } - targetIndex += padddingForByteStride; - } - return newByteBuffer; - } - - /** - * Compute the number of padding bytes that have to be inserted after the - * specified component. - * - * https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#data-alignment - * - * @param elementType The element type - * @param componentType The component type - * @param componentIndex The component index - * @return The padding bytes - */ - private static int computePaddingBytesAfterComponent( - ElementType elementType, int componentType, int componentIndex) - { - int n = Accessors.getNumBytesForAccessorComponentType(componentType); - if (n == 1) - { - if (elementType == ElementType.MAT2) - { - if (componentIndex == 1) - { - return 2; - } - if (componentIndex == 3) - { - return 2; - } - } - if (elementType == ElementType.MAT3) - { - if (componentIndex == 2) - { - return 1; - } - if (componentIndex == 5) - { - return 1; - } - if (componentIndex == 9) - { - return 1; - } - } - } - if (n == 2) - { - if (elementType == ElementType.MAT3) - { - if (componentIndex == 2) - { - return 2; - } - if (componentIndex == 5) - { - return 2; - } - if (componentIndex == 9) - { - return 2; - } - } - } - return 0; - } - - - /** - * Perform a basic validation of the padding/alignment requirements - * of the given {@link BufferModel} - * - * @param bufferModel The {@link BufferModel} - */ - private void validatePadding(BufferModel bufferModel) - { - List bufferViewModels = - bufferStructure.getBufferViewModels(); - - for (BufferViewModel bufferViewModel : bufferViewModels) - { - List accessorModels = - bufferStructure.getAccessorModels(bufferViewModel); - for (AccessorModel accessorModel : accessorModels) - { - validatePadding(bufferViewModel, accessorModel); - } - } - } - - /** - * Perform a basic validation of the padding/alignment requirements - * of the given {@link BufferViewModel} and {@link AccessorModel} - * - * @param bufferViewModel The {@link BufferViewModel} - * @param accessorModel The {@link AccessorModel} - */ - private void validatePadding( - BufferViewModel bufferViewModel, AccessorModel accessorModel) - { - int alignmentBytes = Alignment.computeAlignmentBytes(accessorModel); - - int bufferViewByteOffset = bufferViewModel.getByteOffset(); - int accessorByteOffset = accessorModel.getByteOffset(); - int totalByteOffset = bufferViewByteOffset + accessorByteOffset; - if (accessorByteOffset % alignmentBytes != 0) - { - logger.severe("Error: accessor.byteOffset is " + accessorByteOffset - + " for alignment " + alignmentBytes + " in accessor " - + accessorModel); - } - if (totalByteOffset % alignmentBytes != 0) - { - logger.severe("Error: bufferView.byteOffset+accessor.byteOffset is " - + totalByteOffset + " for alignment " + alignmentBytes - + " in accessor " + accessorModel); - } - } } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureProcessor.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureProcessor.java new file mode 100644 index 00000000..4d06fb0d --- /dev/null +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureProcessor.java @@ -0,0 +1,455 @@ +/* + * www.javagl.de - JglTF + * + * Copyright 2015-2017 Marco Hutter - http://www.javagl.de + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +package de.javagl.jgltf.model.structure; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Logger; + +import de.javagl.jgltf.model.AccessorData; +import de.javagl.jgltf.model.AccessorModel; +import de.javagl.jgltf.model.Accessors; +import de.javagl.jgltf.model.BufferModel; +import de.javagl.jgltf.model.BufferViewModel; +import de.javagl.jgltf.model.ElementType; +import de.javagl.jgltf.model.GltfConstants; +import de.javagl.jgltf.model.impl.DefaultAccessorModel; +import de.javagl.jgltf.model.impl.DefaultBufferModel; +import de.javagl.jgltf.model.impl.DefaultBufferViewModel; +import de.javagl.jgltf.model.io.Buffers; + +/** + * Internal class for processing a {@link BufferStructure} to generate + * the actual buffer data that will eventually be passed to + * {@link DefaultBufferModel#setBufferData(ByteBuffer)}. + * + */ +class BufferStructureProcessor +{ + /** + * The logger used in this class + */ + private static final Logger logger = + Logger.getLogger(BufferStructureProcessor.class.getName()); + + /** + * The {@link BufferStructure} + */ + private final BufferStructure bufferStructure; + + /** + * The mapping from buffer view objects that are intended for images + * (and do not have associated accessors) to their data + */ + private final Map + imageBufferViewDataMap; + + /** + * Creates a new instance + * + * @param bufferStructure The {@link BufferStructure} + * @param imageBufferViewDataMap The mapping from buffer view objects + * that are used for images to their data + */ + BufferStructureProcessor(BufferStructure bufferStructure, + Map imageBufferViewDataMap) + { + this.bufferStructure = Objects.requireNonNull( + bufferStructure, "The bufferStructure may not be null"); + this.imageBufferViewDataMap = Objects.requireNonNull( + imageBufferViewDataMap, + "The imageBufferViewDataMap may not be null"); + } + + /** + * Internal method to finalize the elements of the current + * {@link BufferStructure}: This will compute the byte offsets and + * paddings, and create the actual {@link BufferModel#getBufferData() + * buffer data}. This does a default construction. A method for + * interleaved construction may be added later, in some form. + */ + void process() + { + List bufferModels = + bufferStructure.getBufferModels(); + for (DefaultBufferModel bufferModel : bufferModels) + { + processBufferModel(bufferModel); + } + } + + /** + * Process the given {@link BufferModel} during the build process. + * This will compute the properties of the model, as well as the + * properties of its {@link AccessorModel} and {@link BufferViewModel} + * instances, that can only be determined when the buffer is about + * to be finalized (e.g. the byte stride, byte offsets and lengths). + * + * @param bufferModel The {@link BufferModel} + */ + private void processBufferModel(DefaultBufferModel bufferModel) + { + // Compute the mapping from AccessorModel instances to the + // byte buffers that contain their data in compact form. + // These byte buffers may have to be combined, reordered + // or padded in order to create the actual buffer data + Map rawAccessorModelByteBuffers = + new LinkedHashMap(); + for (DefaultAccessorModel accessorModel : + bufferStructure.getAccessorModels()) + { + AccessorData accessorData = accessorModel.getAccessorData(); + ByteBuffer byteBuffer = accessorData.createByteBuffer(); + rawAccessorModelByteBuffers.put(accessorModel, byteBuffer); + } + + // The sequence of accessor data buffers and paddings that + // eventually will be combined to create the buffer data + List bufferElements = new ArrayList(); + + List bufferViewModels = + bufferStructure.getBufferViewModels(bufferModel); + + int accumulatedBufferBytes = 0; + for (DefaultBufferViewModel bufferViewModel : bufferViewModels) + { + List accessorModels = + bufferStructure.getAccessorModels(bufferViewModel); + + // Handle the padding that may have to be inserted into + // the buffer, before the start of the buffer view: + // The buffer view has to be aligned to the least + // common multiple of the component size of all accessors + int bufferViewAlignmnentBytes = + Alignment.computeAlignmentBytes(accessorModels); + int paddingBytesForBuffer = Alignment.computePadding( + accumulatedBufferBytes, bufferViewAlignmnentBytes); + + if (paddingBytesForBuffer > 0) + { + bufferStructure.addPaddingByteIndices( + bufferModel, accumulatedBufferBytes, paddingBytesForBuffer); + accumulatedBufferBytes += paddingBytesForBuffer; + bufferElements.add(ByteBuffer.allocate(paddingBytesForBuffer)); + } + + bufferViewModel.setByteOffset(accumulatedBufferBytes); + + Integer commonByteStride = null; + Integer target = bufferViewModel.getTarget(); + + // When the target is a vertex attribute, then the elements + // of the accessor must be aligned to a multiple of 4. + boolean targetIsVertexAttribute = Objects.equals( + GltfConstants.GL_ARRAY_BUFFER, target); + if (targetIsVertexAttribute) + { + commonByteStride = + Alignment.computeCommonVertexAttributeByteStride( + accessorModels); + for (DefaultAccessorModel accessorModel : accessorModels) + { + int oldByteStride = accessorModel.getByteStride(); + if (oldByteStride != commonByteStride) + { + accessorModel.setByteStride(commonByteStride); + bufferViewModel.setByteStride(commonByteStride); + } + } + + // When there are multiple vertex attribute accessors that refer + // to the same buffer view, then the byte stride must be defined + if (accessorModels.size() > 1) + { + bufferViewModel.setByteStride(commonByteStride); + } + } + + // Handle buffer view models that do not have associated + // accessors, and are only used for images + if (accessorModels.isEmpty()) + { + ByteBuffer bufferViewData = + imageBufferViewDataMap.get(bufferViewModel); + bufferViewModel.setByteLength(bufferViewData.capacity()); + accumulatedBufferBytes += bufferViewData.capacity(); + bufferElements.add(bufferViewData); + } + else + { + int accumulatedBufferViewBytes = 0; + for (DefaultAccessorModel accessorModel : accessorModels) + { + // Handle the padding that may have to be inserted + // into the buffer view before the start of the + // accessor: + // The accessor has to be aligned to its component size. + int accessorAlignmentBytes = + Alignment.computeAlignmentBytes(accessorModel); + int paddingBytesForBufferView = Alignment.computePadding( + accumulatedBufferViewBytes, accessorAlignmentBytes); + if (paddingBytesForBufferView > 0) + { + // Note: The padding here should always be 0, + // because the buffer view was already padded + logger.warning("Inserting " + paddingBytesForBufferView + + " padding bytes for buffer view, due to accessor " + + accessorModel); + + bufferStructure.addPaddingByteIndices( + bufferModel, accumulatedBufferBytes, + paddingBytesForBufferView); + accumulatedBufferViewBytes += paddingBytesForBufferView; + } + + accessorModel.setByteOffset( + accumulatedBufferViewBytes); + + int targetByteStride = + accessorModel.getPaddedElementSizeInBytes(); + if (commonByteStride == null) + { + accessorModel.setByteStride(targetByteStride); + } + else + { + targetByteStride = commonByteStride; + } + + // Compute the byte buffer for the accessor data. This + // may have to be restructured by inserting padding bytes + ByteBuffer rawAccessorByteBuffer = + rawAccessorModelByteBuffers.get(accessorModel); + ByteBuffer accessorByteBuffer = rawAccessorByteBuffer; + + int count = accessorModel.getCount(); + ElementType elementType = accessorModel.getElementType(); + int componentType = accessorModel.getComponentType(); + accessorByteBuffer = applyPadding( + rawAccessorByteBuffer, count, elementType, + componentType, targetByteStride); + + accumulatedBufferViewBytes += accessorByteBuffer.capacity(); + + if (paddingBytesForBufferView > 0) + { + // Note: The padding here should always be 0, + // because the buffer view was already padded + logger.warning("Inserting " + paddingBytesForBufferView + + " padding bytes for buffer view, due to accessor " + + accessorModel); + + accumulatedBufferBytes += paddingBytesForBufferView; + bufferElements.add( + ByteBuffer.allocate(paddingBytesForBufferView)); + } + accumulatedBufferBytes += accessorByteBuffer.capacity(); + bufferElements.add(accessorByteBuffer); + + } + bufferViewModel.setByteLength( + accumulatedBufferViewBytes); + } + } + + validatePadding(bufferModel); + + // Create the buffer data, and assign it to the buffer + ByteBuffer bufferData = Buffers.concat(bufferElements); + bufferModel.setBufferData(bufferData); + } + + /** + * Read the data from the byte buffer that was created with + * {@link AccessorData#createByteBuffer()} and that contains + * the data in packed form, and write it into a new buffer, + * with the padding bytes that are required according to the + * specification. + * + * @param packedByteBuffer The packed byte buffer + * @param count The number of elements in the accessor + * @param elementType The accessor element type + * @param componentType The component type + * @param byteStride The target byte stride (that must at least + * be equal to the {@link ElementType#getByteStride(int)}, but may be + * larger) + * @return The buffer, with the padding applied + */ + private static ByteBuffer applyPadding( + ByteBuffer packedByteBuffer, int count, ElementType elementType, + int componentType, int byteStride) + { + ByteBuffer newByteBuffer = Buffers.create(count * byteStride); + int numComponents = elementType.getNumComponents(); + int numBytesPerComponent = + Accessors.getNumBytesForAccessorComponentType(componentType); + int sourceIndex = 0; + int targetIndex = 0; + int padddingForByteStride = + byteStride - elementType.getByteStride(componentType); + for (int i = 0; i < count; i++) + { + for (int c = 0; c < numComponents; c++) + { + for (int b = 0; b < numBytesPerComponent; b++) + { + byte value = packedByteBuffer.get(sourceIndex); + newByteBuffer.put(targetIndex, value); + sourceIndex++; + targetIndex++; + } + int padding = computePaddingBytesAfterComponent(elementType, + componentType, c); + targetIndex += padding; + } + targetIndex += padddingForByteStride; + } + return newByteBuffer; + } + + /** + * Compute the number of padding bytes that have to be inserted after the + * specified component. + * + * https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#data-alignment + * + * @param elementType The element type + * @param componentType The component type + * @param componentIndex The component index + * @return The padding bytes + */ + private static int computePaddingBytesAfterComponent( + ElementType elementType, int componentType, int componentIndex) + { + int n = Accessors.getNumBytesForAccessorComponentType(componentType); + if (n == 1) + { + if (elementType == ElementType.MAT2) + { + if (componentIndex == 1) + { + return 2; + } + if (componentIndex == 3) + { + return 2; + } + } + if (elementType == ElementType.MAT3) + { + if (componentIndex == 2) + { + return 1; + } + if (componentIndex == 5) + { + return 1; + } + if (componentIndex == 9) + { + return 1; + } + } + } + if (n == 2) + { + if (elementType == ElementType.MAT3) + { + if (componentIndex == 2) + { + return 2; + } + if (componentIndex == 5) + { + return 2; + } + if (componentIndex == 9) + { + return 2; + } + } + } + return 0; + } + + + /** + * Perform a basic validation of the padding/alignment requirements + * of the given {@link BufferModel} + * + * @param bufferModel The {@link BufferModel} + */ + private void validatePadding(BufferModel bufferModel) + { + List bufferViewModels = + bufferStructure.getBufferViewModels(); + + for (BufferViewModel bufferViewModel : bufferViewModels) + { + List accessorModels = + bufferStructure.getAccessorModels(bufferViewModel); + for (AccessorModel accessorModel : accessorModels) + { + validatePadding(bufferViewModel, accessorModel); + } + } + } + + /** + * Perform a basic validation of the padding/alignment requirements + * of the given {@link BufferViewModel} and {@link AccessorModel} + * + * @param bufferViewModel The {@link BufferViewModel} + * @param accessorModel The {@link AccessorModel} + */ + private void validatePadding( + BufferViewModel bufferViewModel, AccessorModel accessorModel) + { + int alignmentBytes = Alignment.computeAlignmentBytes(accessorModel); + + int bufferViewByteOffset = bufferViewModel.getByteOffset(); + int accessorByteOffset = accessorModel.getByteOffset(); + int totalByteOffset = bufferViewByteOffset + accessorByteOffset; + if (accessorByteOffset % alignmentBytes != 0) + { + logger.severe("Error: accessor.byteOffset is " + accessorByteOffset + + " for alignment " + alignmentBytes + " in accessor " + + accessorModel); + } + if (totalByteOffset % alignmentBytes != 0) + { + logger.severe("Error: bufferView.byteOffset+accessor.byteOffset is " + + totalByteOffset + " for alignment " + alignmentBytes + + " in accessor " + accessorModel); + } + } + +} From a5add9d80d331b0158d9104047f7e4d5847a36eb Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Thu, 13 Nov 2025 15:26:32 +0100 Subject: [PATCH 09/16] Add option for buffer view per attribute --- .../structure/DefaultBufferBuilderStrategy.java | 13 +++++++++++++ .../jgltf/model/structure/GltfModelStructures.java | 1 + 2 files changed, 14 insertions(+) diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/DefaultBufferBuilderStrategy.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/DefaultBufferBuilderStrategy.java index cfeb2daa..0ae5bb7a 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/DefaultBufferBuilderStrategy.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/DefaultBufferBuilderStrategy.java @@ -101,6 +101,11 @@ class DefaultBufferBuilderStrategy implements BufferBuilderStrategy */ static class Config { + /** + * Whether to create one buffer view for each attribute accessor + */ + boolean bufferViewPerAttributeAccessor = false; + /** * Whether to create one buffer per mesh primitive */ @@ -281,6 +286,14 @@ private void processMeshPrimitiveModel( (DefaultAccessorModel)attribute); processedAccessorModels.add(attribute); } + if (config.bufferViewPerAttributeAccessor) + { + if (bufferStructureBuilder.getNumCurrentAccessorModels() > 0) + { + bufferStructureBuilder.createArrayBufferViewModel( + "attributes"); + } + } } if (bufferStructureBuilder.getNumCurrentAccessorModels() > 0) { diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/GltfModelStructures.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/GltfModelStructures.java index 93174bf3..38d38d3e 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/GltfModelStructures.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/GltfModelStructures.java @@ -362,6 +362,7 @@ public DefaultGltfModel createBinary() DefaultBufferBuilderStrategy.Config config = new DefaultBufferBuilderStrategy.Config(); config.imagesInBufferViews = true; + config.bufferViewPerAttributeAccessor = true; return create(config); } From bab396ecef146af0e8e9bef8add4bbb8028a1279 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Thu, 4 Dec 2025 15:23:58 +0100 Subject: [PATCH 10/16] Update gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 76afad62..a1940c4d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,8 @@ /jgltf-experiments-extensions /jgltf-impl-v2-ext-draco-mesh-compression /jgltf-draco-test +/jgltf-impl-v2-ext-meshopt-compression +/jgltf-model-ext-meshopt-compression # --- # Preliminary for extension experiments: From f7b38677392a9ddc5cb87d4b6572ae7fbe27058d Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Thu, 4 Dec 2025 15:24:44 +0100 Subject: [PATCH 11/16] Fix typo in comment --- .../de/javagl/jgltf/model/structure/BufferStructureBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureBuilder.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureBuilder.java index 60faaa9e..13547512 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureBuilder.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferStructureBuilder.java @@ -429,7 +429,7 @@ public void addBufferViewModel( * accessors (i.e. buffer view models that are used for images) * * @param idPrefix The ID prefix for the {@link BufferViewModel} - * @param imageData The iamge data + * @param imageData The image data * @return The {@link BufferViewModel} * @throws IllegalStateException When there are pending accessor models */ From cdf5724666c5034d4852a992a3c5330790ee0b10 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Thu, 4 Dec 2025 15:26:26 +0100 Subject: [PATCH 12/16] Initial draft for model element removal --- .../DefaultMeshGpuInstancingModel.java | 26 ++++++++ .../lights_punctual/DefaultLightModel.java | 9 +++ .../DefaultLightsPunctualModel.java | 10 ++++ .../DefaultMaterialsClearcoatModel.java | 22 +++++++ .../DefaultMaterialsVariantsModel.java | 9 +++ ...ltMeshPrimitiveMaterialsVariantsModel.java | 26 ++++++++ .../DefaultTextureTransformModel.java | 9 +++ .../de/javagl/jgltf/model/ModelElement.java | 8 ++- .../model/gl/impl/DefaultProgramModel.java | 21 +++++++ .../model/gl/impl/DefaultShaderModel.java | 16 +++++ .../model/gl/impl/DefaultTechniqueModel.java | 28 +++++++++ .../model/impl/AbstractModelElement.java | 45 +++++++++++++- .../model/impl/DefaultAccessorModel.java | 15 +++++ .../model/impl/DefaultAnimationModel.java | 32 ++++++++++ .../jgltf/model/impl/DefaultAssetModel.java | 10 ++++ .../jgltf/model/impl/DefaultBufferModel.java | 10 ++++ .../model/impl/DefaultBufferViewModel.java | 16 +++++ .../jgltf/model/impl/DefaultCameraModel.java | 9 +++ .../jgltf/model/impl/DefaultGltfModel.java | 22 +++++++ .../jgltf/model/impl/DefaultImageModel.java | 13 ++++ .../jgltf/model/impl/DefaultMeshModel.java | 10 ++++ .../model/impl/DefaultMeshPrimitiveModel.java | 59 +++++++++++++++++++ .../jgltf/model/impl/DefaultNodeModel.java | 27 ++++++++- .../model/impl/DefaultPbrMaterialModel.java | 25 ++++++++ .../DefaultPbrMetallicRoughnessModel.java | 21 ++++++- .../jgltf/model/impl/DefaultSceneModel.java | 11 ++++ .../jgltf/model/impl/DefaultSkinModel.java | 29 +++++++++ .../impl/DefaultTechniqueMaterialModel.java | 15 +++++ .../model/impl/DefaultTextureInfoModel.java | 13 ++++ .../jgltf/model/impl/DefaultTextureModel.java | 16 +++++ 30 files changed, 578 insertions(+), 4 deletions(-) diff --git a/jgltf-model-ext-mesh-gpu-instancing/src/main/java/de/javagl/jgltf/model/ext/mesh_gpu_instancing/DefaultMeshGpuInstancingModel.java b/jgltf-model-ext-mesh-gpu-instancing/src/main/java/de/javagl/jgltf/model/ext/mesh_gpu_instancing/DefaultMeshGpuInstancingModel.java index f20753e3..7b2fbd44 100644 --- a/jgltf-model-ext-mesh-gpu-instancing/src/main/java/de/javagl/jgltf/model/ext/mesh_gpu_instancing/DefaultMeshGpuInstancingModel.java +++ b/jgltf-model-ext-mesh-gpu-instancing/src/main/java/de/javagl/jgltf/model/ext/mesh_gpu_instancing/DefaultMeshGpuInstancingModel.java @@ -26,9 +26,12 @@ */ package de.javagl.jgltf.model.ext.mesh_gpu_instancing; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Set; @@ -92,4 +95,27 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + Set keysToRemove = new LinkedHashSet(); + for (Entry entry : attributes.entrySet()) + { + String key = entry.getKey(); + AccessorModel value = entry.getValue(); + if (modelElementsToRemove.contains(value)) + { + keysToRemove.add(key); + } + } + for (String keyToRemove : keysToRemove) + { + attributes.remove(keyToRemove); + } + return attributes.isEmpty(); + } + + } diff --git a/jgltf-model-khr-lights-punctual/src/main/java/de/javagl/jgltf/model/khr/lights_punctual/DefaultLightModel.java b/jgltf-model-khr-lights-punctual/src/main/java/de/javagl/jgltf/model/khr/lights_punctual/DefaultLightModel.java index 3c9a93ac..3ad22a42 100644 --- a/jgltf-model-khr-lights-punctual/src/main/java/de/javagl/jgltf/model/khr/lights_punctual/DefaultLightModel.java +++ b/jgltf-model-khr-lights-punctual/src/main/java/de/javagl/jgltf/model/khr/lights_punctual/DefaultLightModel.java @@ -26,6 +26,7 @@ */ package de.javagl.jgltf.model.khr.lights_punctual; +import java.util.Collection; import java.util.Set; import de.javagl.jgltf.model.ModelElement; @@ -114,4 +115,12 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + return false; + } + } diff --git a/jgltf-model-khr-lights-punctual/src/main/java/de/javagl/jgltf/model/khr/lights_punctual/DefaultLightsPunctualModel.java b/jgltf-model-khr-lights-punctual/src/main/java/de/javagl/jgltf/model/khr/lights_punctual/DefaultLightsPunctualModel.java index abafb7ad..4a41e139 100644 --- a/jgltf-model-khr-lights-punctual/src/main/java/de/javagl/jgltf/model/khr/lights_punctual/DefaultLightsPunctualModel.java +++ b/jgltf-model-khr-lights-punctual/src/main/java/de/javagl/jgltf/model/khr/lights_punctual/DefaultLightsPunctualModel.java @@ -27,6 +27,7 @@ package de.javagl.jgltf.model.khr.lights_punctual; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; @@ -88,4 +89,13 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + lightModels.removeAll(modelElementsToRemove); + return lightModels.isEmpty(); + } + } diff --git a/jgltf-model-khr-materials-clearcoat/src/main/java/de/javagl/jgltf/model/khr/materials_clearcoat/DefaultMaterialsClearcoatModel.java b/jgltf-model-khr-materials-clearcoat/src/main/java/de/javagl/jgltf/model/khr/materials_clearcoat/DefaultMaterialsClearcoatModel.java index 22d2d012..662f9e52 100644 --- a/jgltf-model-khr-materials-clearcoat/src/main/java/de/javagl/jgltf/model/khr/materials_clearcoat/DefaultMaterialsClearcoatModel.java +++ b/jgltf-model-khr-materials-clearcoat/src/main/java/de/javagl/jgltf/model/khr/materials_clearcoat/DefaultMaterialsClearcoatModel.java @@ -26,6 +26,7 @@ */ package de.javagl.jgltf.model.khr.materials_clearcoat; +import java.util.Collection; import java.util.Set; import de.javagl.jgltf.model.ModelElement; @@ -149,4 +150,25 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + if (modelElementsToRemove.contains(clearcoatTextureInfoModel)) + { + setClearcoatTextureInfoModel(null); + } + if (modelElementsToRemove.contains(clearcoatRoughnessTextureInfoModel)) + { + setClearcoatRoughnessTextureInfoModel(null); + } + if (modelElementsToRemove.contains(clearcoatNormalTextureInfoModel)) + { + setClearcoatNormalTextureInfoModel(null); + } + return false; + } + + } diff --git a/jgltf-model-khr-materials-variants/src/main/java/de/javagl/jgltf/model/khr/materials_variants/DefaultMaterialsVariantsModel.java b/jgltf-model-khr-materials-variants/src/main/java/de/javagl/jgltf/model/khr/materials_variants/DefaultMaterialsVariantsModel.java index 11e007d5..7b9d45d1 100644 --- a/jgltf-model-khr-materials-variants/src/main/java/de/javagl/jgltf/model/khr/materials_variants/DefaultMaterialsVariantsModel.java +++ b/jgltf-model-khr-materials-variants/src/main/java/de/javagl/jgltf/model/khr/materials_variants/DefaultMaterialsVariantsModel.java @@ -27,6 +27,7 @@ package de.javagl.jgltf.model.khr.materials_variants; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; @@ -88,5 +89,13 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + return false; + } + } diff --git a/jgltf-model-khr-materials-variants/src/main/java/de/javagl/jgltf/model/khr/materials_variants/DefaultMeshPrimitiveMaterialsVariantsModel.java b/jgltf-model-khr-materials-variants/src/main/java/de/javagl/jgltf/model/khr/materials_variants/DefaultMeshPrimitiveMaterialsVariantsModel.java index b2e89f93..ed61d702 100644 --- a/jgltf-model-khr-materials-variants/src/main/java/de/javagl/jgltf/model/khr/materials_variants/DefaultMeshPrimitiveMaterialsVariantsModel.java +++ b/jgltf-model-khr-materials-variants/src/main/java/de/javagl/jgltf/model/khr/materials_variants/DefaultMeshPrimitiveMaterialsVariantsModel.java @@ -26,8 +26,11 @@ */ package de.javagl.jgltf.model.khr.materials_variants; +import java.util.Collection; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import de.javagl.jgltf.model.MaterialModel; @@ -110,4 +113,27 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + Set keysToRemove = new LinkedHashSet(); + for (Entry entry : materials.entrySet()) + { + String key = entry.getKey(); + MaterialModel value = entry.getValue(); + if (modelElementsToRemove.contains(value)) + { + keysToRemove.add(key); + } + } + for (String keyToRemove : keysToRemove) + { + materials.remove(keyToRemove); + } + return materials.isEmpty(); + } + + } \ No newline at end of file diff --git a/jgltf-model-khr-texture-transform/src/main/java/de/javagl/jgltf/model/khr/texture_transform/DefaultTextureTransformModel.java b/jgltf-model-khr-texture-transform/src/main/java/de/javagl/jgltf/model/khr/texture_transform/DefaultTextureTransformModel.java index 546567d8..8bd0d7fd 100644 --- a/jgltf-model-khr-texture-transform/src/main/java/de/javagl/jgltf/model/khr/texture_transform/DefaultTextureTransformModel.java +++ b/jgltf-model-khr-texture-transform/src/main/java/de/javagl/jgltf/model/khr/texture_transform/DefaultTextureTransformModel.java @@ -26,6 +26,7 @@ */ package de.javagl.jgltf.model.khr.texture_transform; +import java.util.Collection; import java.util.Set; import de.javagl.jgltf.model.ModelElement; @@ -122,5 +123,13 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + return false; + } + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/ModelElement.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/ModelElement.java index 927b9a73..6f895d3b 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/ModelElement.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/ModelElement.java @@ -26,6 +26,7 @@ */ package de.javagl.jgltf.model; +import java.util.Collection; import java.util.Map; import java.util.Set; @@ -91,7 +92,12 @@ public interface ModelElement * {@link ModelElement} objects that are directly referred to * by this {@link ModelElement}. * - * @return The list + * @return The set */ Set getReferencedModelElements(); + + boolean removeModelElements( + Collection modelElementsToRemove); + + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/gl/impl/DefaultProgramModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/gl/impl/DefaultProgramModel.java index c08d182a..0478bf3b 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/gl/impl/DefaultProgramModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/gl/impl/DefaultProgramModel.java @@ -27,6 +27,7 @@ package de.javagl.jgltf.model.gl.impl; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -132,5 +133,25 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + boolean removeThis = false; + if (modelElementsToRemove.contains(vertexShaderModel)) + { + setVertexShaderModel(null); + removeThis = true; + } + if (modelElementsToRemove.contains(fragmentShaderModel)) + { + setVertexShaderModel(null); + removeThis = true; + } + return removeThis; + } + + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/gl/impl/DefaultShaderModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/gl/impl/DefaultShaderModel.java index d5e78005..330c3631 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/gl/impl/DefaultShaderModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/gl/impl/DefaultShaderModel.java @@ -27,6 +27,7 @@ package de.javagl.jgltf.model.gl.impl; import java.nio.ByteBuffer; +import java.util.Collection; import java.util.Set; import de.javagl.jgltf.model.BufferViewModel; @@ -168,4 +169,19 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + boolean removeThis = false; + if (modelElementsToRemove.contains(bufferViewModel)) + { + setBufferViewModel(null); + removeThis = true; + } + return removeThis; + } + + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/gl/impl/DefaultTechniqueModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/gl/impl/DefaultTechniqueModel.java index b47684f3..4c573fee 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/gl/impl/DefaultTechniqueModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/gl/impl/DefaultTechniqueModel.java @@ -26,6 +26,7 @@ */ package de.javagl.jgltf.model.gl.impl; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -214,5 +215,32 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + boolean removeThis = false; + for (TechniqueParametersModel parameter : parameters.values()) + { + NodeModel nodeModel = parameter.getNodeModel(); + if (modelElementsToRemove.contains(nodeModel)) + { + removeThis = true; + } + Object value = parameter.getValue(); + if (value instanceof ModelElement) + { + ModelElement modelElement = (ModelElement) value; + if (modelElementsToRemove.contains(modelElement)) + { + removeThis = true; + } + } + } + return removeThis; + } + + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/AbstractModelElement.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/AbstractModelElement.java index 3dfbe434..9b2e5046 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/AbstractModelElement.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/AbstractModelElement.java @@ -26,10 +26,12 @@ */ package de.javagl.jgltf.model.impl; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.logging.Logger; @@ -225,7 +227,10 @@ public T getExtensionModel(String extensionName, * found as values in the {@link #getExtensionModels()} map. * * This serves as the basis for {@link #getReferencedModelElements()} - * implementations. + * implementations. Classes implementing the {@link ModelElement} + * interface will implement {@link #getReferencedModelElements()} + * by calling this function, and then adding their referenced + * model elements to the returned set. * * @return The set */ @@ -247,5 +252,43 @@ protected final Set getReferencedExtensionModelElements() return modelElements; } + +// @Override +// public boolean removeModelElements( +// Collection modelElementsToRemove) +// { +// System.out.println("XXX DEFAULT IMPLEMENTATION OF removeModelElements FOR "+this); +// return false; +// } + // TODO to be called at the start of removeModelElements + protected final void removeExtensionModelElements( + Collection modelElementsToRemove) + { + if (extensionModels == null) + { + return; + } + Set keysToRemove = new LinkedHashSet(); + for (Entry entry : extensionModels.entrySet()) + { + String key = entry.getKey(); + Object value = entry.getValue(); + if (value instanceof ModelElement) + { + ModelElement modelElement = (ModelElement) value; + boolean removeExtensionObject = + modelElement.removeModelElements(modelElementsToRemove); + if (removeExtensionObject + || modelElementsToRemove.contains(modelElement)) + { + keysToRemove.add(key); + } + } + } + for (String keyToRemove : keysToRemove) + { + extensionModels.remove(keyToRemove); + } + } } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultAccessorModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultAccessorModel.java index 153da48e..6d6cc072 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultAccessorModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultAccessorModel.java @@ -26,6 +26,7 @@ */ package de.javagl.jgltf.model.impl; +import java.util.Collection; import java.util.Set; import de.javagl.jgltf.model.AccessorData; @@ -267,4 +268,18 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + boolean removeThis = false; + if (modelElementsToRemove.contains(bufferViewModel)) + { + setBufferViewModel(null); + removeThis = true; + } + return removeThis; + } + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultAnimationModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultAnimationModel.java index f52e5bf0..4a56ebb9 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultAnimationModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultAnimationModel.java @@ -27,7 +27,9 @@ package de.javagl.jgltf.model.impl; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -224,4 +226,34 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + Set channelsToRemove = new LinkedHashSet(); + for (Channel channel : channels) + { + NodeModel nodeModel = channel.getNodeModel(); + if (modelElementsToRemove.contains(nodeModel)) + { + channelsToRemove.add(channel); + } + Sampler sampler = channel.getSampler(); + AccessorModel input = sampler.getInput(); + if (modelElementsToRemove.contains(input)) + { + channelsToRemove.add(channel); + } + AccessorModel output = sampler.getOutput(); + if (modelElementsToRemove.contains(output)) + { + channelsToRemove.add(channel); + } + } + channels.removeAll(channelsToRemove); + return !channelsToRemove.isEmpty(); + } + + } \ No newline at end of file diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultAssetModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultAssetModel.java index 97693b4f..471d2c13 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultAssetModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultAssetModel.java @@ -26,6 +26,7 @@ */ package de.javagl.jgltf.model.impl; +import java.util.Collection; import java.util.Set; import de.javagl.jgltf.model.AssetModel; @@ -87,4 +88,13 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + return false; + } + + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultBufferModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultBufferModel.java index 498d3bde..3e051e5c 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultBufferModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultBufferModel.java @@ -27,6 +27,7 @@ package de.javagl.jgltf.model.impl; import java.nio.ByteBuffer; +import java.util.Collection; import java.util.Set; import de.javagl.jgltf.model.BufferModel; @@ -102,4 +103,13 @@ public Set getReferencedModelElements() getReferencedExtensionModelElements(); return modelElements; } + + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + return false; + } + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultBufferViewModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultBufferViewModel.java index d30cfe0c..30dde832 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultBufferViewModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultBufferViewModel.java @@ -27,6 +27,7 @@ package de.javagl.jgltf.model.impl; import java.nio.ByteBuffer; +import java.util.Collection; import java.util.Set; import java.util.function.Consumer; @@ -202,4 +203,19 @@ public Set getReferencedModelElements() } return modelElements; } + + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + boolean removeThis = false; + if (modelElementsToRemove.contains(bufferModel)) + { + setBufferModel(null); + removeThis = true; + } + return removeThis; + } + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultCameraModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultCameraModel.java index 5d58849b..8b215231 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultCameraModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultCameraModel.java @@ -26,6 +26,7 @@ */ package de.javagl.jgltf.model.impl; +import java.util.Collection; import java.util.Set; import java.util.function.DoubleSupplier; import java.util.function.Supplier; @@ -123,4 +124,12 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + return false; + } + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultGltfModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultGltfModel.java index a87f6790..135bd08b 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultGltfModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultGltfModel.java @@ -887,4 +887,26 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + + accessorModels.removeAll(modelElementsToRemove); + animationModels.removeAll(modelElementsToRemove); + bufferModels.removeAll(modelElementsToRemove); + bufferViewModels.removeAll(modelElementsToRemove); + cameraModels.removeAll(modelElementsToRemove); + imageModels.removeAll(modelElementsToRemove); + materialModels.removeAll(modelElementsToRemove); + meshModels.removeAll(modelElementsToRemove); + nodeModels.removeAll(modelElementsToRemove); + sceneModels.removeAll(modelElementsToRemove); + skinModels.removeAll(modelElementsToRemove); + textureModels.removeAll(modelElementsToRemove); + + return false; + } + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultImageModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultImageModel.java index 3e33ffe0..648fbcd7 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultImageModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultImageModel.java @@ -27,6 +27,7 @@ package de.javagl.jgltf.model.impl; import java.nio.ByteBuffer; +import java.util.Collection; import java.util.Set; import de.javagl.jgltf.model.BufferViewModel; @@ -148,4 +149,16 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + if (modelElementsToRemove.contains(bufferViewModel)) + { + setBufferViewModel(null); + } + return false; + } + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultMeshModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultMeshModel.java index ee856d7b..b16e5d91 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultMeshModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultMeshModel.java @@ -27,6 +27,7 @@ package de.javagl.jgltf.model.impl; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; @@ -111,4 +112,13 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + meshPrimitiveModels.removeAll(modelElementsToRemove); + return meshPrimitiveModels.isEmpty(); + } + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultMeshPrimitiveModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultMeshPrimitiveModel.java index 9609fd02..a0cb93d0 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultMeshPrimitiveModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultMeshPrimitiveModel.java @@ -27,10 +27,13 @@ package de.javagl.jgltf.model.impl; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Set; @@ -204,5 +207,61 @@ public Set getReferencedModelElements() } return modelElements; } + + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + boolean removeThis = true; + if (modelElementsToRemove.contains(indices)) + { + setIndices(null); + removeThis = true; + } + + Set attributeKeysToRemove = new LinkedHashSet(); + for (Entry entry : attributes.entrySet()) + { + String key = entry.getKey(); + Object value = entry.getValue(); + if (modelElementsToRemove.contains(value)) + { + attributeKeysToRemove.add(key); + } + } + for (String attributeKeyToRemove : attributeKeysToRemove) + { + attributes.remove(attributeKeyToRemove); + } + if (attributes.isEmpty()) + { + removeThis = true; + } + + Set> targetsToRemove = + new LinkedHashSet>(); + for (Map target : targets) + { + for (String attributeKeyToRemove : attributeKeysToRemove) + { + if (target.containsKey(attributeKeyToRemove)) + { + targetsToRemove.add(target); + } + } + for (Entry entry : target.entrySet()) + { + Object value = entry.getValue(); + if (modelElementsToRemove.contains(value)) + { + targetsToRemove.add(target); + } + } + } + targets.removeAll(targetsToRemove); + return removeThis; + } + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultNodeModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultNodeModel.java index 7bae0d56..d77cfbae 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultNodeModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultNodeModel.java @@ -27,6 +27,7 @@ package de.javagl.jgltf.model.impl; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -301,6 +302,30 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + children.removeAll(modelElementsToRemove); + meshModels.removeAll(modelElementsToRemove); + if (modelElementsToRemove.contains(cameraModel)) + { + setCameraModel(null); + } + if (modelElementsToRemove.contains(skinModel)) + { + setSkinModel(null); + } + boolean removeThis = true; + removeThis &= children.isEmpty(); + removeThis &= meshModels.isEmpty(); + removeThis &= (cameraModel != null); + removeThis &= (skinModel != null); + return removeThis; + } + + @Override public double[] computeLocalTransform(double result[]) { @@ -326,7 +351,7 @@ public Supplier createLocalTransformSupplier() return Suppliers.createTransformSupplier(this, NodeModel::computeLocalTransform); } - + /** * Compute the local transform of the given node. The transform * is either taken from the {@link #getMatrix()} (if it is not diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultPbrMaterialModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultPbrMaterialModel.java index 4e748144..18b8ef82 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultPbrMaterialModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultPbrMaterialModel.java @@ -26,6 +26,7 @@ */ package de.javagl.jgltf.model.impl; +import java.util.Collection; import java.util.Set; import de.javagl.jgltf.model.ModelElement; @@ -251,4 +252,28 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + if (modelElementsToRemove.contains(pbrMetallicRoughnessModel)) + { + setPbrMetallicRoughnessModel(null); + } + if (modelElementsToRemove.contains(normalTextureInfoModel)) + { + setNormalTextureInfoModel(null); + } + if (modelElementsToRemove.contains(occlusionTextureInfoModel)) + { + setOcclusionTextureInfoModel(null); + } + if (modelElementsToRemove.contains(emissiveTextureInfoModel)) + { + setEmissiveFactor(null); + } + return false; + } + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultPbrMetallicRoughnessModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultPbrMetallicRoughnessModel.java index 61de5736..366b662a 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultPbrMetallicRoughnessModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultPbrMetallicRoughnessModel.java @@ -26,6 +26,7 @@ */ package de.javagl.jgltf.model.impl; +import java.util.Collection; import java.util.Set; import de.javagl.jgltf.model.ModelElement; @@ -102,7 +103,8 @@ public TextureInfoModel getBaseColorTextureInfoModel() * * @param baseColorTextureInfoModel The base color texture info model */ - public void setBaseColorTextureInfoModel(TextureInfoModel baseColorTextureInfoModel) + public void setBaseColorTextureInfoModel( + TextureInfoModel baseColorTextureInfoModel) { this.baseColorTextureInfoModel = baseColorTextureInfoModel; } @@ -174,4 +176,21 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + if (modelElementsToRemove.contains(baseColorTextureInfoModel)) + { + setBaseColorTextureInfoModel(null); + } + if (modelElementsToRemove.contains(metallicRoughnessTextureInfoModel)) + { + setBaseColorTextureInfoModel(null); + } + return false; + } + + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultSceneModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultSceneModel.java index 9952521c..4805f745 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultSceneModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultSceneModel.java @@ -27,6 +27,7 @@ package de.javagl.jgltf.model.impl; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; @@ -79,4 +80,14 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + nodeModels.removeAll(modelElementsToRemove); + return nodeModels.isEmpty(); + } + + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultSkinModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultSkinModel.java index 268160a2..f9bd9051 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultSkinModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultSkinModel.java @@ -27,6 +27,7 @@ package de.javagl.jgltf.model.impl; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -184,5 +185,33 @@ public Set getReferencedModelElements() } return modelElements; } + + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + boolean removeThis = false; + for (NodeModel joint : joints) + { + if (modelElementsToRemove.contains(joint)) + { + joints.clear(); + removeThis = true; + } + } + if (modelElementsToRemove.contains(skeleton)) + { + setSkeleton(null); + removeThis = true; + } + if (modelElementsToRemove.contains(inverseBindMatrices)) + { + setInverseBindMatrices(null); + removeThis = true; + } + return removeThis; + } + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultTechniqueMaterialModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultTechniqueMaterialModel.java index 07dc0895..6378b6cb 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultTechniqueMaterialModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultTechniqueMaterialModel.java @@ -26,6 +26,7 @@ */ package de.javagl.jgltf.model.impl; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -113,4 +114,18 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + boolean removeThis = false; + if (modelElementsToRemove.contains(techniqueModel)) + { + setTechniqueModel(null); + removeThis = true; + } + return removeThis; + } + } \ No newline at end of file diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultTextureInfoModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultTextureInfoModel.java index e9cd9c10..12b8576c 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultTextureInfoModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultTextureInfoModel.java @@ -26,6 +26,7 @@ */ package de.javagl.jgltf.model.impl; +import java.util.Collection; import java.util.Set; import de.javagl.jgltf.model.ImageModel; @@ -101,4 +102,16 @@ public Set getReferencedModelElements() return modelElements; } + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + if (modelElementsToRemove.contains(textureModel)) + { + setTextureModel(null); + } + return false; + } + } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultTextureModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultTextureModel.java index 1f8003fd..64e5989c 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultTextureModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultTextureModel.java @@ -26,6 +26,7 @@ */ package de.javagl.jgltf.model.impl; +import java.util.Collection; import java.util.Set; import de.javagl.jgltf.model.ImageModel; @@ -162,4 +163,19 @@ public Set getReferencedModelElements() } return modelElements; } + + @Override + public boolean removeModelElements( + Collection modelElementsToRemove) + { + removeExtensionModelElements(modelElementsToRemove); + boolean removeThis = false; + if (modelElementsToRemove.contains(imageModel)) + { + setImageModel(null); + removeThis = true; + } + return removeThis; + } + } From baf7b9e74a6c0aa1a045b7f5039e6c4b194aa8cf Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sun, 7 Dec 2025 14:39:11 +0100 Subject: [PATCH 13/16] Move configuration to top-level class --- .../model/structure/BufferBuilderConfig.java | 70 +++++++++++++++++++ .../structure/BufferBuilderStrategies.java | 6 +- .../DefaultBufferBuilderStrategy.java | 70 +------------------ .../model/structure/GltfModelStructures.java | 14 ++-- 4 files changed, 82 insertions(+), 78 deletions(-) create mode 100644 jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferBuilderConfig.java diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferBuilderConfig.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferBuilderConfig.java new file mode 100644 index 00000000..81448672 --- /dev/null +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferBuilderConfig.java @@ -0,0 +1,70 @@ +package de.javagl.jgltf.model.structure; + +/** + * A package-private class storing the configuration settings for + * a {@link DefaultBufferBuilderStrategy}. + * + * This class is not part of the public API. + */ +class BufferBuilderConfig +{ + /** + * Whether to create one buffer view for each attribute accessor + */ + boolean bufferViewPerAttributeAccessor = false; + + /** + * Whether to create one buffer per mesh primitive + */ + boolean bufferPerMeshPrimitive = false; + + /** + * Whether to create one buffer per mesh + */ + boolean bufferPerMesh = false; + + /** + * Whether to create one buffer for all meshes + */ + boolean bufferForMeshes = false; + + /** + * Whether to create one buffer per animation + */ + boolean bufferPerAnimation = false; + + /** + * Whether to create one buffer for all animations + */ + boolean bufferForAnimations = false; + + /** + * Whether to create one buffer per skin + */ + boolean bufferPerSkin = false; + + /** + * Whether to create one buffer for all skins + */ + boolean bufferForSkins = false; + + /** + * Whether images should be stored in buffer views + */ + boolean imagesInBufferViews = false; + + /** + * Whether to create one buffer per image + */ + boolean bufferPerImage = false; + + /** + * Whether to create one buffer for all images + */ + boolean bufferForImages = false; + + /** + * Whether to create one buffer for all additional accessors + */ + boolean bufferForAdditionalAccessors = false; +} diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferBuilderStrategies.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferBuilderStrategies.java index 6c8b5e96..31cd5bb1 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferBuilderStrategies.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/BufferBuilderStrategies.java @@ -41,8 +41,8 @@ public class BufferBuilderStrategies */ public static BufferBuilderStrategy createDefault() { - DefaultBufferBuilderStrategy.Config config = - new DefaultBufferBuilderStrategy.Config(); + BufferBuilderConfig config = + new BufferBuilderConfig(); return new DefaultBufferBuilderStrategy(config); } @@ -54,7 +54,7 @@ public static BufferBuilderStrategy createDefault() * @return The {@link BufferBuilderStrategy} */ static BufferBuilderStrategy create( - DefaultBufferBuilderStrategy.Config config) + BufferBuilderConfig config) { return new DefaultBufferBuilderStrategy(config); } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/DefaultBufferBuilderStrategy.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/DefaultBufferBuilderStrategy.java index 0ae5bb7a..9644b9da 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/DefaultBufferBuilderStrategy.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/DefaultBufferBuilderStrategy.java @@ -96,83 +96,17 @@ class DefaultBufferBuilderStrategy implements BufferBuilderStrategy */ private final Set existingImageUriStrings; - /** - * A package-private class storing the configuration settings - */ - static class Config - { - /** - * Whether to create one buffer view for each attribute accessor - */ - boolean bufferViewPerAttributeAccessor = false; - - /** - * Whether to create one buffer per mesh primitive - */ - boolean bufferPerMeshPrimitive = false; - - /** - * Whether to create one buffer per mesh - */ - boolean bufferPerMesh = false; - - /** - * Whether to create one buffer for all meshes - */ - boolean bufferForMeshes = false; - - /** - * Whether to create one buffer per animation - */ - boolean bufferPerAnimation = false; - - /** - * Whether to create one buffer for all animations - */ - boolean bufferForAnimations = false; - - /** - * Whether to create one buffer per skin - */ - boolean bufferPerSkin = false; - - /** - * Whether to create one buffer for all skins - */ - boolean bufferForSkins = false; - - /** - * Whether images should be stored in buffer views - */ - boolean imagesInBufferViews = false; - - /** - * Whether to create one buffer per image - */ - boolean bufferPerImage = false; - - /** - * Whether to create one buffer for all images - */ - boolean bufferForImages = false; - - /** - * Whether to create one buffer for all additional accessors - */ - boolean bufferForAdditionalAccessors = false; - } - /** * The configuration settings */ - private final Config config; + private final BufferBuilderConfig config; /** * Default constructor * * @param config The configuration */ - DefaultBufferBuilderStrategy(Config config) + DefaultBufferBuilderStrategy(BufferBuilderConfig config) { this.config = config; diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/GltfModelStructures.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/GltfModelStructures.java index 38d38d3e..141dbd48 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/GltfModelStructures.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/structure/GltfModelStructures.java @@ -342,8 +342,8 @@ public void prepare(GltfModel sourceGltfModel) */ public DefaultGltfModel createDefault() { - DefaultBufferBuilderStrategy.Config config = - new DefaultBufferBuilderStrategy.Config(); + BufferBuilderConfig config = + new BufferBuilderConfig(); return create(config); } @@ -359,8 +359,8 @@ public DefaultGltfModel createDefault() */ public DefaultGltfModel createBinary() { - DefaultBufferBuilderStrategy.Config config = - new DefaultBufferBuilderStrategy.Config(); + BufferBuilderConfig config = + new BufferBuilderConfig(); config.imagesInBufferViews = true; config.bufferViewPerAttributeAccessor = true; return create(config); @@ -374,8 +374,8 @@ public DefaultGltfModel createBinary() */ public DefaultGltfModel createCustom() { - DefaultBufferBuilderStrategy.Config config = - new DefaultBufferBuilderStrategy.Config(); + BufferBuilderConfig config = + new BufferBuilderConfig(); config.bufferForAnimations = true; config.bufferForMeshes = true; @@ -395,7 +395,7 @@ public DefaultGltfModel createCustom() * @param config The configuration * @return The restructured model */ - private DefaultGltfModel create(DefaultBufferBuilderStrategy.Config config) + private DefaultGltfModel create(BufferBuilderConfig config) { if (this.target == null) { From b7baa89d8290a809912665edc803d905664c3e8b Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sun, 7 Dec 2025 14:39:34 +0100 Subject: [PATCH 14/16] Fix model element removal handling --- .../jgltf/model/impl/DefaultMeshPrimitiveModel.java | 2 +- .../de/javagl/jgltf/model/impl/DefaultNodeModel.java | 11 ++++++----- .../jgltf/model/impl/DefaultTextureInfoModel.java | 1 + 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultMeshPrimitiveModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultMeshPrimitiveModel.java index a0cb93d0..cd272db6 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultMeshPrimitiveModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultMeshPrimitiveModel.java @@ -213,7 +213,7 @@ public boolean removeModelElements( Collection modelElementsToRemove) { removeExtensionModelElements(modelElementsToRemove); - boolean removeThis = true; + boolean removeThis = false; if (modelElementsToRemove.contains(indices)) { setIndices(null); diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultNodeModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultNodeModel.java index d77cfbae..e93b2b7a 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultNodeModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultNodeModel.java @@ -317,11 +317,12 @@ public boolean removeModelElements( { setSkinModel(null); } - boolean removeThis = true; - removeThis &= children.isEmpty(); - removeThis &= meshModels.isEmpty(); - removeThis &= (cameraModel != null); - removeThis &= (skinModel != null); + boolean removeThis = false; + if (children.isEmpty() && meshModels.isEmpty() && cameraModel == null + && skinModel == null) + { + removeThis = true; + } return removeThis; } diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultTextureInfoModel.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultTextureInfoModel.java index 12b8576c..5530d891 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultTextureInfoModel.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/impl/DefaultTextureInfoModel.java @@ -110,6 +110,7 @@ public boolean removeModelElements( if (modelElementsToRemove.contains(textureModel)) { setTextureModel(null); + return true; } return false; } From 6864aed9ce532d0b997d2e9e7838ca86dea59f1e Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sun, 7 Dec 2025 14:39:46 +0100 Subject: [PATCH 15/16] Documentation for model element removal --- .../de/javagl/jgltf/model/ModelElement.java | 76 +++++++++++-------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/jgltf-model/src/main/java/de/javagl/jgltf/model/ModelElement.java b/jgltf-model/src/main/java/de/javagl/jgltf/model/ModelElement.java index 6f895d3b..3a7c9320 100644 --- a/jgltf-model/src/main/java/de/javagl/jgltf/model/ModelElement.java +++ b/jgltf-model/src/main/java/de/javagl/jgltf/model/ModelElement.java @@ -31,54 +31,52 @@ import java.util.Set; /** - * Interface for all classes of the model package. This is corresponds to - * the GlTFProperty of the original glTF asset. + * Interface for all classes of the model package. This is corresponds to the + * GlTFProperty of the original glTF asset. */ public interface ModelElement { /** - * Returns the extensions of this element. This is a mapping from - * property names (extension names) to the JSON objects. + * Returns the extensions of this element. This is a mapping from property + * names (extension names) to the JSON objects. * - * This will be a reference to the map that is stored internally. - * Clients should usually not access or modify this map. Library - * developers may access this map, for example, to remove extension - * information, but are then responsible for ensuring consistency - * by updating other model elements, or the extension model of - * the root glTF model. + * This will be a reference to the map that is stored internally. Clients + * should usually not access or modify this map. Library developers may + * access this map, for example, to remove extension information, but are + * then responsible for ensuring consistency by updating other model + * elements, or the extension model of the root glTF model. * * @return The extensions */ Map getExtensions(); - + /** - * Returns the extras of this element. + * Returns the extras of this element. * * @return The extras */ Object getExtras(); - + /** - * Returns an unmodifiable map containing the extension models of this - * element, or null if this object does not have any - * extension models. + * Returns an unmodifiable map containing the extension models of this + * element, or null if this object does not have any extension + * models. * - * This is a mapping from property names (extension names) - * to the model objects that have been created from the extension - * information. + * This is a mapping from property names (extension names) to the model + * objects that have been created from the extension information. * * @return The extension models */ Map getExtensionModels(); - + /** * Returns the model object that is stored for the given extension name. * - * If this object does not have an object for the specified extension, - * then null is returned. + * If this object does not have an object for the specified extension, then + * null is returned. * - * If the extension object is not an instance of the given type, then - * a warning is printed and null is returned. + * If the extension object is not an instance of the given type, then a + * warning is printed and null is returned. * * @param The model object type * @param extensionName The extension name @@ -86,18 +84,34 @@ public interface ModelElement * @return The model object */ T getExtensionModel(String extensionName, Class type); - + /** - * Returns a possibly unmodifiable and possibly empty set of all - * {@link ModelElement} objects that are directly referred to - * by this {@link ModelElement}. + * Returns a possibly unmodifiable and possibly empty set of all + * {@link ModelElement} objects that are directly referred to by this + * {@link ModelElement}. * * @return The set */ Set getReferencedModelElements(); - + + /** + * Remove the given {@link ModelElement} objects from this object. + * + * Any direct references that this model element has to one of the given + * elements will be removed (for example, by setting them to + * null or removing them from internal collections where + * appropriate) + * + * The return value indicates whether the removal of the given model + * elements implies the removal of this model element. This means that this + * model element became "invalid" or "obsolete" due to the removal of the + * given elements, meaning that this model element should also be removed + * from all other model elements. + * + * @param modelElementsToRemove The model elements to remove + * @return Whether this model element has to be removed afterwards + */ boolean removeModelElements( Collection modelElementsToRemove); - - + } From 99488f60a424930f304541d8bfcad76d68e57e51 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sun, 7 Dec 2025 14:41:12 +0100 Subject: [PATCH 16/16] Drafts for model transform package --- .gitignore | 6 + jgltf-model-transform/README.md | 6 + jgltf-model-transform/pom.xml | 50 ++++ .../model/transform/GltfModelPruner.java | 217 ++++++++++++++++++ .../model/transform/GltfModelTransforms.java | 120 ++++++++++ .../model/transform/graph/DefaultEdge.java | 71 ++++++ .../model/transform/graph/DefaultGraph.java | 208 +++++++++++++++++ .../jgltf/model/transform/graph/Edge.java | 52 +++++ .../jgltf/model/transform/graph/Graph.java | 81 +++++++ .../jgltf/model/transform/graph/Graphs.java | 157 +++++++++++++ .../model/transform/graph/MutableGraph.java | 73 ++++++ .../transform/graph/model/ModelEdge.java | 51 ++++ .../transform/graph/model/ModelGraph.java | 48 ++++ .../transform/graph/model/ModelGraphs.java | 85 +++++++ .../transform/graph/model/package-info.java | 7 + .../model/transform/graph/package-info.java | 7 + .../jgltf/model/transform/package-info.java | 5 + 17 files changed, 1244 insertions(+) create mode 100644 jgltf-model-transform/README.md create mode 100644 jgltf-model-transform/pom.xml create mode 100644 jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/GltfModelPruner.java create mode 100644 jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/GltfModelTransforms.java create mode 100644 jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/DefaultEdge.java create mode 100644 jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/DefaultGraph.java create mode 100644 jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/Edge.java create mode 100644 jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/Graph.java create mode 100644 jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/Graphs.java create mode 100644 jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/MutableGraph.java create mode 100644 jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/model/ModelEdge.java create mode 100644 jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/model/ModelGraph.java create mode 100644 jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/model/ModelGraphs.java create mode 100644 jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/model/package-info.java create mode 100644 jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/package-info.java create mode 100644 jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/package-info.java diff --git a/.gitignore b/.gitignore index a1940c4d..eac88298 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,12 @@ #/jgltf-model-khr-materials-variants #/jgltf-model-khr-materials-clearcoat/ #/jgltf-model-khr-texture-transform + +/jgltf-model-transform/.classpath +/jgltf-model-transform/.settings +/jgltf-model-transform/.project +/jgltf-model-transform/target + # --- /jgltf-impl-v2-ext-mesh-gpu-instancing/.classpath diff --git a/jgltf-model-transform/README.md b/jgltf-model-transform/README.md new file mode 100644 index 00000000..5ed32d8b --- /dev/null +++ b/jgltf-model-transform/README.md @@ -0,0 +1,6 @@ +# jgltf-model-transform + +Classes for transforming glTF model instances + +**Note:** This library is still subject to change. + diff --git a/jgltf-model-transform/pom.xml b/jgltf-model-transform/pom.xml new file mode 100644 index 00000000..2515327c --- /dev/null +++ b/jgltf-model-transform/pom.xml @@ -0,0 +1,50 @@ + + 4.0.0 + + + de.javagl + jgltf-parent + 2.0.5-SNAPSHOT + + + jgltf-model-transform + jar + + + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + + + + de.javagl + jgltf-model + 2.0.5-SNAPSHOT + + + de.javagl + jgltf-impl-v1 + 2.0.5-SNAPSHOT + + + de.javagl + jgltf-impl-v2 + 2.0.5-SNAPSHOT + + + junit + junit + 4.13.2 + test + + + + \ No newline at end of file diff --git a/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/GltfModelPruner.java b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/GltfModelPruner.java new file mode 100644 index 00000000..072c7bdc --- /dev/null +++ b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/GltfModelPruner.java @@ -0,0 +1,217 @@ +/* + * www.javagl.de - JglTF + * + * Copyright 2015-2017 Marco Hutter - http://www.javagl.de + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +package de.javagl.jgltf.model.transform; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import de.javagl.jgltf.model.ModelElement; +import de.javagl.jgltf.model.impl.DefaultGltfModel; +import de.javagl.jgltf.model.transform.graph.Graphs; +import de.javagl.jgltf.model.transform.graph.model.ModelGraph; +import de.javagl.jgltf.model.transform.graph.model.ModelGraphs; + +/** + * A class with methods for pruning a {@link DefaultGltfModel}. + */ +class GltfModelPruner +{ + /** + * The logger used in this class + */ + private static final Logger logger = + Logger.getLogger(GltfModelPruner.class.getName()); + + /** + * Prune the given glTF model. + * + * This will remove all elements in the model that are not reachable from + * either the scene- or the animation models, and it will remove the given + * elements. + * + * This will iteratively remove all model elements that became invalid or + * obsolete due to the removal of the given elements. + * + * @param gltfModel The glTF model + * @param toRemove The elements to remove. If this is null, + * then only a pruning of the current elements will be performed. + */ + static void prune(DefaultGltfModel gltfModel, + Collection toRemove) + { + Level level = Level.INFO; + + logger.info("Building model graph..."); + + ModelGraph g = ModelGraphs.build(gltfModel); + + logger.info("Building model graph DONE"); + if (logger.isLoggable(level)) + { + //logger.log(level, Graphs.createString(g)); + } + + // During traversal and reachability computations, ignore the + // GltfModel itself (because all elements are reachable from it), + // and the AssetModel (because it can never be pruned, and does + // not refer to other elements) + Set ignored = new LinkedHashSet(); + ignored.add(gltfModel); + ignored.add(gltfModel.getAssetModel()); + + // Start the traversal for the reachability computations at the scene- + // and animation models + Set starting = new LinkedHashSet(); + starting.addAll(gltfModel.getSceneModels()); + starting.addAll(gltfModel.getAnimationModels()); + + // The set of elements that have to be removed in the next iteration + Set nextToRemove = new LinkedHashSet(); + if (toRemove != null) + { + nextToRemove.addAll(toRemove); + } + + // All elements that have been removed so far + Set allRemoved = new LinkedHashSet(); + + // Iterate the removal until there are no more unreachable or "invalid" + // elements + int iteration = 0; + while (true) + { + logger.fine("Iteration " + iteration + "..."); + + // Compute the reachable and unreachable elements + Set reachable = + Graphs.computeReachableForward(g, starting, ignored); + Set unreachable = + new LinkedHashSet(g.vertices()); + unreachable.removeAll(ignored); + unreachable.removeAll(reachable); + + if (logger.isLoggable(level)) + { + logger.log(level, createString("Reachable", reachable)); + logger.log(level, createString("Unreachable", unreachable)); + } + + // Determine the elements to remove. These are the unreachable + // elements, as well as the ones that have to be removed + // explicitly from the previous iteration + Set currentToRemove = + new LinkedHashSet(); + currentToRemove.addAll(unreachable); + if (!nextToRemove.isEmpty()) + { + if (logger.isLoggable(level)) + { + logger.log(level, createString("Manual or implied removal", + nextToRemove)); + } + currentToRemove.addAll(nextToRemove); + nextToRemove.clear(); + } + + // If there is nothing to remove, stop the iteration + if (currentToRemove.isEmpty()) + { + logger.info("No more elements to remove after iteration " + + iteration + ", DONE"); + break; + } + + if (logger.isLoggable(level)) + { + logger.log(level, + createString("Total elements to removel", currentToRemove)); + } + + allRemoved.addAll(currentToRemove); + + // Remove the elements from each other model element + for (ModelElement v : g.vertices()) + { + // If the model element becomes invalid or obsolete due to + // the removal (and it was not already removed), then track + // it as an element to remove in the next iteration + boolean impliedRemoval = v.removeModelElements(currentToRemove); + if (impliedRemoval) + { + if (!allRemoved.contains(v)) + { + if (logger.isLoggable(level)) + { + logger.log(level, "Implied removal of " + v + + " due to removal of " + currentToRemove); + } + nextToRemove.add(v); + } + } + } + + // Update the graph by removing the vertices (model elements) + for (ModelElement v : currentToRemove) + { + g.removeVertex(v); + } + + logger.info("Iteration " + iteration + " DONE"); + iteration++; + } + } + + /** + * Create an unspecified debug string from the given elements + * + * @param name The name of the elements + * @param elements The elements + * @return The result + */ + private static String createString(String name, Collection elements) + { + StringBuilder sb = new StringBuilder(); + sb.append(name + " (" + elements.size() + ")"); + for (Object element : elements) + { + sb.append("\n" + " " + element); + } + return sb.toString(); + } + + /** + * Private constructor to prevent instantiation + */ + private GltfModelPruner() + { + // Private constructor to prevent instantiation + } + +} diff --git a/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/GltfModelTransforms.java b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/GltfModelTransforms.java new file mode 100644 index 00000000..96f6c744 --- /dev/null +++ b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/GltfModelTransforms.java @@ -0,0 +1,120 @@ +/* + * www.javagl.de - JglTF + * + * Copyright 2015-2017 Marco Hutter - http://www.javagl.de + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +package de.javagl.jgltf.model.transform; + +import java.util.Collection; + +import de.javagl.jgltf.model.AccessorModel; +import de.javagl.jgltf.model.ImageModel; +import de.javagl.jgltf.model.ModelElement; +import de.javagl.jgltf.model.impl.DefaultAccessorModel; +import de.javagl.jgltf.model.impl.DefaultGltfModel; +import de.javagl.jgltf.model.impl.DefaultImageModel; +import de.javagl.jgltf.model.structure.BufferBuilderStrategies; +import de.javagl.jgltf.model.structure.BufferBuilderStrategy; + +/** + * Methods to transform {@link DefaultGltfModel} instances + */ +public class GltfModelTransforms +{ + /** + * Prune the given glTF model. + * + * This will remove all elements in the model that are not reachable from + * either the scene- or the animation models. + * + * This will iteratively remove all model elements that became invalid + * or obsolete due to the removal of the given elements. + * + * @param gltfModel The glTF model + */ + public static void prune(DefaultGltfModel gltfModel) + { + GltfModelPruner.prune(gltfModel, null); + rebuildBufferStructure(gltfModel); + } + + /** + * Remove the given elements from the given glTF model. + * + * This will iteratively remove all model elements that became invalid + * or obsolete due to the removal of the given elements. + * + * @param gltfModel The glTF model + * @param toRemove The elements to remove. If this is null, + * then only a pruning of the current elements will be performed. + */ + public static void removeAll(DefaultGltfModel gltfModel, + Collection toRemove) + { + GltfModelPruner.prune(gltfModel, toRemove); + rebuildBufferStructure(gltfModel); + } + + /** + * Rebuild the buffer structure for the given glTF model. + * + * This will update all accessor models in the given model to refer + * to freshly built buffer view- and buffer models. + * + * Some details of this process are intentionally not specified. + * + * @param gltfModel The glTF model + */ + private static void rebuildBufferStructure(DefaultGltfModel gltfModel) + { + BufferBuilderStrategy bbs = BufferBuilderStrategies.createDefault(); + + for (AccessorModel am : gltfModel.getAccessorModels()) + { + DefaultAccessorModel dam = (DefaultAccessorModel) am; + dam.setBufferViewModel(null); + } + gltfModel.clearBufferModels(); + gltfModel.clearBufferViewModels(); + + bbs.process(gltfModel); + + for (ImageModel im : gltfModel.getImageModels()) + { + DefaultImageModel dim = (DefaultImageModel) im; + bbs.validateImageModel(dim); + } + gltfModel.addBufferViewModels(bbs.getBufferViewModels()); + gltfModel.addBufferModels(bbs.getBufferModels()); + + } + + /** + * Private constructor to prevent instantiation + */ + private GltfModelTransforms() + { + // Private constructor to prevent instantiation + } +} diff --git a/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/DefaultEdge.java b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/DefaultEdge.java new file mode 100644 index 00000000..1fbcf65a --- /dev/null +++ b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/DefaultEdge.java @@ -0,0 +1,71 @@ +/* + * www.javagl.de - JglTF + * + * Copyright 2015-2017 Marco Hutter - http://www.javagl.de + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +package de.javagl.jgltf.model.transform.graph; + +import java.util.Objects; + +/** + * Default implementation of an {@link Edge} + * + * @param The vertex type + */ +public class DefaultEdge implements Edge +{ + /** + * The first vertex + */ + private final V v0; + + /** + * The second vertex + */ + private final V v1; + + /** + * Creates a new instance + * + * @param v0 The first vertex + * @param v1 The second vertex + */ + public DefaultEdge(V v0, V v1) + { + this.v0 = Objects.requireNonNull(v0, "Vertex 0 may not be null"); + this.v1 = Objects.requireNonNull(v1, "Vertex 1 may not be null"); + } + + @Override + public V v0() + { + return v0; + } + + @Override + public V v1() + { + return v1; + } +} diff --git a/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/DefaultGraph.java b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/DefaultGraph.java new file mode 100644 index 00000000..169b15ca --- /dev/null +++ b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/DefaultGraph.java @@ -0,0 +1,208 @@ +/* + * www.javagl.de - JglTF + * + * Copyright 2015-2017 Marco Hutter - http://www.javagl.de + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +package de.javagl.jgltf.model.transform.graph; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Default implementation of a {@link Graph} + * + * This class is not part of the public API. + * + * @param The vertex type + * @param The edge type + */ +public class DefaultGraph> implements MutableGraph +{ + /** + * The vertices + */ + private final Set vertices; + + /** + * The edges + */ + private final Set edges; + + /** + * The outgoing edges + */ + private final Map> outgoingEdgesOf; + + /** + * The incoming edges + */ + private final Map> incomingEdgesOf; + + /** + * The function that creates the edges + */ + private final BiFunction edgeFactory; + + /** + * Default constructor + * + * @param edgeFactory The edge factory + */ + public DefaultGraph(BiFunction edgeFactory) + { + this.vertices = new LinkedHashSet(); + this.edges = new LinkedHashSet(); + this.outgoingEdgesOf = new LinkedHashMap>(); + this.incomingEdgesOf = new LinkedHashMap>(); + this.edgeFactory = Objects.requireNonNull(edgeFactory, "The edgeFactory may not be null"); + } + + @Override + public void addVertex(V v) + { + Objects.requireNonNull(v, "The vertex may not be null"); + this.vertices.add(v); + } + + @Override + public void removeVertex(V v) + { + this.vertices.remove(v); + Set edgesToRemove = new LinkedHashSet(); + List outgoingEdges = outgoingEdgesOf.get(v); + if (outgoingEdges != null) + { + edgesToRemove.addAll(outgoingEdges); + } + List incomingEdges = incomingEdgesOf.get(v); + if (incomingEdges != null) + { + edgesToRemove.addAll(incomingEdges); + } + for (E e : edgesToRemove) + { + removeEdge(e.v0(), e.v1()); + } + } + + @Override + public Set vertices() + { + return Collections.unmodifiableSet(vertices); + } + + /** + * Add the specified edge to this graph + * + * @param v0 The first vertex + * @param v1 The second vertex + */ + @Override + public void addEdge(V v0, V v1) + { + E e = edgeFactory.apply(v0, v1); + this.edges.add(e); + Function> mappingFunction = (k) -> new ArrayList(); + outgoingEdgesOf.computeIfAbsent(v0, mappingFunction).add(e); + incomingEdgesOf.computeIfAbsent(v1, mappingFunction).add(e); + } + + @Override + public void removeEdge(V v0, V v1) + { + List outgoingEdges = outgoingEdgesOf.get(v0); + if (outgoingEdges != null) + { + Set edgesToRemove = new LinkedHashSet(); + for (E e : outgoingEdges) + { + if (Objects.equals(e.v1(), v1)) + { + edgesToRemove.add(e); + edges.remove(e); + } + } + outgoingEdges.removeAll(edgesToRemove); + if (outgoingEdges.isEmpty()) + { + outgoingEdgesOf.remove(v0); + } + } + List incomingEdges = incomingEdgesOf.get(v1); + if (incomingEdges != null) + { + Set edgesToRemove = new LinkedHashSet(); + for (E e : incomingEdges) + { + if (Objects.equals(e.v0(), v0)) + { + edgesToRemove.add(e); + edges.remove(e); + } + } + incomingEdges.removeAll(edgesToRemove); + if (incomingEdges.isEmpty()) + { + incomingEdgesOf.remove(v1); + } + } + } + + @Override + public Set edges() + { + return Collections.unmodifiableSet(edges); + } + + @Override + public Collection outgoingEdgesOf(V v) + { + List result = outgoingEdgesOf.get(v); + if (result == null) + { + return Collections.emptySet(); + } + return Collections.unmodifiableCollection(result); + } + + @Override + public Collection incomingEdgesOf(V v) + { + List result = incomingEdgesOf.get(v); + if (result == null) + { + return Collections.emptySet(); + } + return Collections.unmodifiableCollection(result); + } +} diff --git a/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/Edge.java b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/Edge.java new file mode 100644 index 00000000..a7d383eb --- /dev/null +++ b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/Edge.java @@ -0,0 +1,52 @@ +/* + * www.javagl.de - JglTF + * + * Copyright 2015-2017 Marco Hutter - http://www.javagl.de + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +package de.javagl.jgltf.model.transform.graph; + +/** + * Interface for an edge in a graph. + * + * This class is not part of the public API. + * + * @param The vertex type + */ +public interface Edge +{ + /** + * Returns the first vertex + * + * @return The vertex + */ + V v0(); + + /** + * Returns the second vertex + * + * @return The vertex + */ + V v1(); +} + diff --git a/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/Graph.java b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/Graph.java new file mode 100644 index 00000000..fa96240b --- /dev/null +++ b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/Graph.java @@ -0,0 +1,81 @@ +/* + * www.javagl.de - JglTF + * + * Copyright 2015-2017 Marco Hutter - http://www.javagl.de + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +package de.javagl.jgltf.model.transform.graph; + +import java.util.Collection; +import java.util.Set; + +/** + * Interface for a basic graph. + * + * Unless otherwise noted, all methods in this interface have to be assumed + * to return unmodifiable collections, and possibly unmodifiable views on + * an internal state. + * + * This class is not part of the public API. + * + * @param The vertex type + * @param The edge type + */ +public interface Graph> +{ + /** + * Returns the vertices. + * + * @return The vertices + */ + Set vertices(); + + /** + * Returns the edges. + * + * @return The edges. + */ + Set edges(); + + /** + * Returns the outgoing edges of the given vertex. + * + * Returns an empty collection if the given vertex is not part of this + * graph. + * + * @param v The vertex + * @return The edges + */ + Collection outgoingEdgesOf(V v); + + /** + * Returns the incoming edges of the given vertex. + * + * Returns an empty collection if the given vertex is not part of this + * graph. + * + * @param v The vertex + * @return The edges + */ + Collection incomingEdgesOf(V v); +} diff --git a/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/Graphs.java b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/Graphs.java new file mode 100644 index 00000000..0cafe1a6 --- /dev/null +++ b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/Graphs.java @@ -0,0 +1,157 @@ +/* + * www.javagl.de - JglTF + * + * Copyright 2015-2017 Marco Hutter - http://www.javagl.de + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +package de.javagl.jgltf.model.transform.graph; + +import java.util.Collection; +import java.util.Deque; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.Set; + +/** + * Utility methods related to {@link Graph} instances + * + * This class is not part of the public API. + */ +public class Graphs +{ + /** + * Prints the given graph to the console, only for debugging. + * + * @param The vertex type + * @param The edge type + * @param g The graph + */ + public static > void print(Graph g) + { + System.out.println(createString(g)); + } + + /** + * Creates an unspecified string representation of the given graph, only for + * debugging. + * + * @param The vertex type + * @param The edge type + * @param g The graph + * @return The string + */ + public static > String createString(Graph g) + { + StringBuilder sb = new StringBuilder(); + for (V v : g.vertices()) + { + Collection outgoing = g.outgoingEdgesOf(v); + Collection incoming = g.incomingEdgesOf(v); + + sb.append("For " + v + ": " + "\n"); + sb.append(" outgoing:" + "\n"); + if (outgoing.isEmpty()) + { + sb.append(" " + "(none)" + "\n"); + } + for (E e : outgoing) + { + sb.append(" " + e.v1() + "\n"); + } + sb.append(" incoming:" + "\n"); + if (incoming.isEmpty()) + { + sb.append(" " + "(none)" + "\n"); + } + for (E e : incoming) + { + sb.append(" " + e.v0() + "\n"); + } + } + return sb.toString(); + } + + /** + * Compute all vertices that are reachable from the given starting vertices + * using only outgoing edges. + * + * @param The vertex type + * @param The edge type + * @param g The graph + * @param startingVertices The starting vertices + * @param ignoredVertices Vertices that should be ignored + * @return The reachable vertices + */ + public static > Set computeReachableForward( + Graph g, Set startingVertices, Set ignoredVertices) + { + Set visited = new LinkedHashSet(); + visited.addAll(ignoredVertices); + for (V v : startingVertices) + { + traverseForward(g, v, visited); + } + visited.removeAll(ignoredVertices); + return visited; + } + + /** + * Traverse the given graph, in unspecified order, starting at the given + * root vertex, using only outgoing edges. + * + * @param The vertex type + * @param The edge type + * @param g The graph + * @param root The root vertex + * @param visited The visited vertices + */ + private static > void traverseForward(Graph g, + V root, Set visited) + { + Deque queue = new LinkedList(); + visited.add(root); + queue.add(root); + while (!queue.isEmpty()) + { + V v = queue.removeFirst(); + Collection outgoingEdges = g.outgoingEdgesOf(v); + for (E e : outgoingEdges) + { + V w = e.v1(); + if (!visited.contains(w)) + { + visited.add(w); + queue.add(w); + } + } + } + } + + /** + * Private constructor to prevent instantiation + */ + private Graphs() + { + // Private constructor to prevent instantiation + } +} diff --git a/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/MutableGraph.java b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/MutableGraph.java new file mode 100644 index 00000000..d9c67d2a --- /dev/null +++ b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/MutableGraph.java @@ -0,0 +1,73 @@ +/* + * www.javagl.de - JglTF + * + * Copyright 2015-2017 Marco Hutter - http://www.javagl.de + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +package de.javagl.jgltf.model.transform.graph; + +/** + * Interface for a {@link Graph} that may be modified + * + * This class is not part of the public API. + * + * @param The vertex type + * @param The edge type + */ +public interface MutableGraph> extends Graph +{ + + /** + * Add the given vertex to this graph + * + * @param v The vertex + */ + void addVertex(V v); + + /** + * Add the specified edge to this graph + * + * @param v0 The first vertex + * @param v1 The second vertex + */ + void addEdge(V v0, V v1); + + /** + * Remove the given vertex from this graph. + * + * This will also cause the removal of all incoming and outgoing edges + * of the given vertex. + * + * @param v The vertex + */ + void removeVertex(V v); + + /** + * Remove the specified edge from this graph + * + * @param v0 The first vertex + * @param v1 The second vertex + */ + void removeEdge(V v0, V v1); + +} \ No newline at end of file diff --git a/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/model/ModelEdge.java b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/model/ModelEdge.java new file mode 100644 index 00000000..02b45154 --- /dev/null +++ b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/model/ModelEdge.java @@ -0,0 +1,51 @@ +/* + * www.javagl.de - JglTF + * + * Copyright 2015-2017 Marco Hutter - http://www.javagl.de + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +package de.javagl.jgltf.model.transform.graph.model; + +import de.javagl.jgltf.model.ModelElement; +import de.javagl.jgltf.model.transform.graph.DefaultEdge; +import de.javagl.jgltf.model.transform.graph.Edge; +import de.javagl.jgltf.model.transform.graph.Graph; + +/** + * An {@link Edge} in a {@link Graph} for a glTF model. + * + * This class is not part of the public API. + */ +class ModelEdge extends DefaultEdge +{ + /** + * Creates a new instance + * + * @param v0 The first vertex + * @param v1 The second vertex + */ + ModelEdge(ModelElement v0, ModelElement v1) + { + super(v0, v1); + } +} diff --git a/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/model/ModelGraph.java b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/model/ModelGraph.java new file mode 100644 index 00000000..c38525be --- /dev/null +++ b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/model/ModelGraph.java @@ -0,0 +1,48 @@ +/* + * www.javagl.de - JglTF + * + * Copyright 2015-2017 Marco Hutter - http://www.javagl.de + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +package de.javagl.jgltf.model.transform.graph.model; + +import de.javagl.jgltf.model.ModelElement; +import de.javagl.jgltf.model.transform.graph.DefaultGraph; +import de.javagl.jgltf.model.transform.graph.Graph; + +/** + * A {@link Graph} of glTF {@link ModelElement} objects. + * + * This class is not part of the public API. + */ +public class ModelGraph extends DefaultGraph +{ + /** + * Default constructor + */ + ModelGraph() + { + super((v0, v1) -> new ModelEdge(v0, v1)); + } +} + diff --git a/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/model/ModelGraphs.java b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/model/ModelGraphs.java new file mode 100644 index 00000000..fc4ed650 --- /dev/null +++ b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/model/ModelGraphs.java @@ -0,0 +1,85 @@ +/* + * www.javagl.de - JglTF + * + * Copyright 2015-2017 Marco Hutter - http://www.javagl.de + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +package de.javagl.jgltf.model.transform.graph.model; + +import java.util.Set; + +import de.javagl.jgltf.model.GltfModel; +import de.javagl.jgltf.model.ModelElement; + +/** + * Methods related to {@link ModelGraph} objects. + * + * This class is not part of the public API. + */ +public class ModelGraphs +{ + /** + * Build a {@link ModelGraph} from the given model + * + * @param model The mode + * @return The {@link ModelGraph} + */ + public static ModelGraph build(GltfModel model) + { + ModelGraph g = new ModelGraph(); + build(g, model); + return g; + } + + /** + * Fill the given model graph with the data from the given model element, + * called recursively + * + * @param g The graph + * @param current The current model element + */ + static void build(ModelGraph g, ModelElement current) + { + //System.out.println("buildGraph from "+current); + if (g.vertices().contains(current)) + { + return; + } + g.addVertex(current); + Set nexts = current.getReferencedModelElements(); + for (ModelElement next : nexts) + { + g.addEdge(current, next); + build(g, next); + } + } + + /** + * Private constructor to prevent instantiation + */ + private ModelGraphs() + { + // Private constructor to prevent instantiation + } + +} diff --git a/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/model/package-info.java b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/model/package-info.java new file mode 100644 index 00000000..56df6578 --- /dev/null +++ b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/model/package-info.java @@ -0,0 +1,7 @@ +/** + * Internal classes representing a simple graph structure for glTF models + *
    + * This package is not part of the public API. + */ +package de.javagl.jgltf.model.transform.graph.model; + diff --git a/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/package-info.java b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/package-info.java new file mode 100644 index 00000000..d11cdf82 --- /dev/null +++ b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/graph/package-info.java @@ -0,0 +1,7 @@ +/** + * Internal classes representing a simple graph structure + *
    + * This package is not part of the public API. + */ +package de.javagl.jgltf.model.transform.graph; + diff --git a/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/package-info.java b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/package-info.java new file mode 100644 index 00000000..0b3915fb --- /dev/null +++ b/jgltf-model-transform/src/main/java/de/javagl/jgltf/model/transform/package-info.java @@ -0,0 +1,5 @@ +/** + * Classes for transforming glTF models + */ +package de.javagl.jgltf.model.transform; +