From a88c38e65fe57f436ed45bfef8dfc840125c6c9f Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Sat, 18 May 2024 13:57:39 +0200 Subject: [PATCH] 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