From be787f5c6c441104665709e88df66c80c3689b45 Mon Sep 17 00:00:00 2001 From: Jesper Smith Date: Thu, 27 Jul 2017 17:42:01 -0500 Subject: [PATCH 01/10] Added supported for custom IO Systems in Java. Implemented ClassLoader IO System --- port/jassimp/jassimp-native/src/jassimp.cpp | 258 +++++++++++++++++- port/jassimp/jassimp-native/src/jassimp.h | 2 +- .../src/jassimp/AiClassLoaderIOSystem.java | 130 +++++++++ .../jassimp/src/jassimp/AiIOStream.java | 55 ++++ .../jassimp/src/jassimp/AiIOSystem.java | 54 ++++ .../src/jassimp/AiInputStreamIOStream.java | 102 +++++++ port/jassimp/jassimp/src/jassimp/Jassimp.java | 36 ++- 7 files changed, 619 insertions(+), 18 deletions(-) create mode 100644 port/jassimp/jassimp/src/jassimp/AiClassLoaderIOSystem.java create mode 100644 port/jassimp/jassimp/src/jassimp/AiIOStream.java create mode 100644 port/jassimp/jassimp/src/jassimp/AiIOSystem.java create mode 100644 port/jassimp/jassimp/src/jassimp/AiInputStreamIOStream.java diff --git a/port/jassimp/jassimp-native/src/jassimp.cpp b/port/jassimp/jassimp-native/src/jassimp.cpp index 226e416aa..475f6c5a0 100644 --- a/port/jassimp/jassimp-native/src/jassimp.cpp +++ b/port/jassimp/jassimp-native/src/jassimp.cpp @@ -1,7 +1,9 @@ #include "jassimp.h" -#include +#include #include +#include +#include #ifdef JNI_LOG @@ -12,9 +14,11 @@ #define lprintf(...) printf (__VA_ARGS__) #endif /* ANDROID */ #else -#define lprintf +#define lprintf #endif +static std::string gLastErrorString; + // Automatically deletes a local ref when it goes out of scope class SmartLocalRef { private: @@ -270,6 +274,81 @@ static bool callv(JNIEnv *env, jobject object, const char* typeName, return true; } +static jobject callo(JNIEnv *env, jobject object, const char* typeName, const char* methodName, + const char* signature,/* const*/ jvalue* params) +{ + jclass clazz = env->FindClass(typeName); + SmartLocalRef clazzRef(env, clazz); + + if (NULL == clazz) + { + lprintf("could not find class %s\n", typeName); + return NULL; + } + + jmethodID mid = env->GetMethodID(clazz, methodName, signature); + + if (NULL == mid) + { + lprintf("could not find method %s with signature %s in type %s\n", methodName, signature, typeName); + return NULL; + } + + jobject jReturnValue = env->CallObjectMethodA(object, mid, params); + + return jReturnValue; +} + +static int calli(JNIEnv *env, jobject object, const char* typeName, const char* methodName, + const char* signature) +{ + jclass clazz = env->FindClass(typeName); + SmartLocalRef clazzRef(env, clazz); + + if (NULL == clazz) + { + lprintf("could not find class %s\n", typeName); + return false; + } + + jmethodID mid = env->GetMethodID(clazz, methodName, signature); + + if (NULL == mid) + { + lprintf("could not find method %s with signature %s in type %s\n", methodName, signature, typeName); + return false; + } + + jint jReturnValue = env->CallIntMethod(object, mid); + + return (int) jReturnValue; +} + +static int callc(JNIEnv *env, jobject object, const char* typeName, const char* methodName, + const char* signature) +{ + jclass clazz = env->FindClass(typeName); + SmartLocalRef clazzRef(env, clazz); + + if (NULL == clazz) + { + lprintf("could not find class %s\n", typeName); + return false; + } + + jmethodID mid = env->GetMethodID(clazz, methodName, signature); + + if (NULL == mid) + { + lprintf("could not find method %s with signature %s in type %s\n", methodName, signature, typeName); + return false; + } + + jint jReturnValue = env->CallCharMethod(object, mid); + + return (int) jReturnValue; +} + static bool callStaticObject(JNIEnv *env, const char* typeName, const char* methodName, const char* signature,/* const*/ jvalue* params, jobject& returnValue) @@ -359,6 +438,155 @@ static bool copyBufferArray(JNIEnv *env, jobject jMesh, const char* jBufferName, return true; } +class JavaIOStream : public Assimp::IOStream +{ +private: + size_t pos; + size_t size; + char* buffer; + jobject jIOStream; + + +public: + JavaIOStream(size_t size, char* buffer, jobject jIOStream) : + pos(0), + size(size), + buffer(buffer), + jIOStream(jIOStream) + {}; + + + ~JavaIOStream(void) + { + free(buffer); + }; + + size_t Read(void* pvBuffer, size_t pSize, size_t pCount) + { + const size_t cnt = std::min(pCount,(size - pos)/pSize); + const size_t ofs = pSize*cnt; + + memcpy(pvBuffer, buffer + pos, ofs); + pos += ofs; + + return cnt; + }; + size_t Write(const void* pvBuffer, size_t pSize, size_t pCount) {}; + + aiReturn Seek(size_t pOffset, aiOrigin pOrigin) + { + if (aiOrigin_SET == pOrigin) { + if (pOffset >= size) { + return AI_FAILURE; + } + pos = pOffset; + } + else if (aiOrigin_END == pOrigin) { + if (pOffset >= size) { + return AI_FAILURE; + } + pos = size-pOffset; + } + else { + if (pOffset + pos >= size) { + return AI_FAILURE; + } + pos += pOffset; + } + return AI_SUCCESS; + }; + + size_t Tell(void) const + { + return pos; + }; + + size_t FileSize() const + { + return size; + }; + + void Flush() {}; + + + jobject javaObject() + { + return jIOStream; + }; + + +}; + + +class JavaIOSystem : public Assimp::IOSystem { + private: + JNIEnv* mJniEnv; + jobject& mJavaIOSystem; + + public: + JavaIOSystem(JNIEnv* env, jobject& javaIOSystem) : + mJniEnv(env), + mJavaIOSystem(javaIOSystem) + {}; + + bool Exists( const char* pFile) const + { + jvalue params[1]; + params[0].l = mJniEnv->NewStringUTF(pFile); + return call(mJniEnv, mJavaIOSystem, "jassimp/AiIOSystem", "exists", "(Ljava/lang/String;)Z", params); + + }; + char getOsSeparator() const + { + return (char) callc(mJniEnv, mJavaIOSystem, "jassimp/AiIOSystem", "getOsSeparator", "()C"); + }; + + Assimp::IOStream* Open(const char* pFile,const char* pMode = "rb") + { + jvalue params[2]; + params[0].l = mJniEnv->NewStringUTF(pFile); + params[1].l = mJniEnv->NewStringUTF(pMode); + + + jobject jStream = callo(mJniEnv, mJavaIOSystem, "jassimp/AiIOSystem", "open", "(Ljava/lang/String;Ljava/lang/String;)Ljassimp/AiIOStream;", params); + if(NULL == jStream) + { + lprintf("NULL object from AiIOSystem.open\n"); + return NULL; + } + + size_t size = calli(mJniEnv, jStream, "jassimp/AiIOStream", "getFileSize", "()I"); + lprintf("Model file size is %d\n", size); + + char* buffer = (char*)malloc(size); + jobject javaBuffer = mJniEnv->NewDirectByteBuffer(buffer, size); + + jvalue readParams[1]; + readParams[0].l = javaBuffer; + if(call(mJniEnv, jStream, "jassimp/AiIOStream", "read", "(Ljava/nio/ByteBuffer;)Z", readParams)) + { + return new JavaIOStream(size, buffer, jStream); + } + else + { + lprintf("Read failure on AiIOStream.read"); + free(buffer); + return NULL; + } + + }; + void Close( Assimp::IOStream* pFile) + { + + jvalue params[1]; + params[0].l = ((JavaIOStream*) pFile)->javaObject(); + callv(mJniEnv, mJavaIOSystem, "jassimp/AiIOSystem", "close", "(Ljassimp/AiIOStream;)V", params); + delete pFile; + }; + + + +}; static bool loadMeshes(JNIEnv *env, const aiScene* cScene, jobject& jScene) @@ -1474,7 +1702,7 @@ JNIEXPORT jint JNICALL Java_jassimp_Jassimp_getlongsize JNIEXPORT jstring JNICALL Java_jassimp_Jassimp_getErrorString (JNIEnv *env, jclass jClazz) { - const char *err = aiGetErrorString(); + const char *err = gLastErrorString.c_str(); if (NULL == err) { @@ -1486,18 +1714,26 @@ JNIEXPORT jstring JNICALL Java_jassimp_Jassimp_getErrorString JNIEXPORT jobject JNICALL Java_jassimp_Jassimp_aiImportFile - (JNIEnv *env, jclass jClazz, jstring jFilename, jlong postProcess) + (JNIEnv *env, jclass jClazz, jstring jFilename, jlong postProcess, jobject ioSystem) { jobject jScene = NULL; /* convert params */ const char* cFilename = env->GetStringUTFChars(jFilename, NULL); + + Assimp::Importer imp; - + + if(ioSystem != NULL) + { + imp.SetIOHandler(new JavaIOSystem(env, ioSystem)); + lprintf("Created aiFileIO\n"); + } + lprintf("opening file: %s\n", cFilename); /* do import */ - const aiScene *cScene = aiImportFile(cFilename, (unsigned int) postProcess); + const aiScene *cScene = imp.ReadFile(cFilename, (unsigned int) postProcess); if (!cScene) { @@ -1552,19 +1788,13 @@ error: /* thats really a problem because we cannot throw in this case */ env->FatalError("could not throw java.io.IOException"); } - - env->ThrowNew(exception, aiGetErrorString()); + gLastErrorString = imp.GetErrorString(); + env->ThrowNew(exception, gLastErrorString.c_str()); lprintf("problem detected\n"); } end: - /* - * NOTE: this releases all memory used in the native domain. - * Ensure all data has been passed to java before! - */ - aiReleaseImport(cScene); - /* free params */ env->ReleaseStringUTFChars(jFilename, cFilename); diff --git a/port/jassimp/jassimp-native/src/jassimp.h b/port/jassimp/jassimp-native/src/jassimp.h index f448dc2c2..7d4b66e29 100644 --- a/port/jassimp/jassimp-native/src/jassimp.h +++ b/port/jassimp/jassimp-native/src/jassimp.h @@ -39,7 +39,7 @@ JNIEXPORT jstring JNICALL Java_jassimp_Jassimp_getErrorString * Signature: (Ljava/lang/String;J)Ljassimp/AiScene; */ JNIEXPORT jobject JNICALL Java_jassimp_Jassimp_aiImportFile - (JNIEnv *, jclass, jstring, jlong); + (JNIEnv *, jclass, jstring, jlong, jobject); #ifdef __cplusplus } diff --git a/port/jassimp/jassimp/src/jassimp/AiClassLoaderIOSystem.java b/port/jassimp/jassimp/src/jassimp/AiClassLoaderIOSystem.java new file mode 100644 index 000000000..04d638610 --- /dev/null +++ b/port/jassimp/jassimp/src/jassimp/AiClassLoaderIOSystem.java @@ -0,0 +1,130 @@ +/* + * Copyright 2017 Florida Institute for Human and Machine Cognition (IHMC) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package jassimp; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +/** + * IOSystem based on the Java classloader.

+ * + * This IOSystem allows loading models directly from the + * classpath. No extraction to the file system is + * necessary. + * + * @author Jesper Smith + * + */ +public class AiClassLoaderIOSystem implements AiIOSystem +{ + private final Class clazz; + private final ClassLoader classLoader; + + /** + * Construct a new AiClassLoaderIOSystem.

+ * + * This constructor uses a ClassLoader to resolve + * resources. + * + * @param classLoader classLoader to resolve resources. + */ + public AiClassLoaderIOSystem(ClassLoader classLoader) { + this.clazz = null; + this.classLoader = classLoader; + } + + /** + * Construct a new AiClassLoaderIOSystem.

+ * + * This constructor uses a Class to resolve + * resources. + * + * @param class class to resolve resources. + */ + public AiClassLoaderIOSystem(Class clazz) { + this.clazz = clazz; + this.classLoader = null; + } + + + @Override + public AiInputStreamIOStream open(String filename, String ioMode) { + try { + + InputStream is; + + if(clazz != null) { + is = clazz.getResourceAsStream(filename); + } + else if (classLoader != null) { + is = classLoader.getResourceAsStream(filename); + } + else { + System.err.println("[" + getClass().getSimpleName() + + "] No class or classLoader provided to resolve " + filename); + return null; + } + + if(is != null) { + return new AiInputStreamIOStream(is); + } + else { + System.err.println("[" + getClass().getSimpleName() + + "] Cannot find " + filename); + return null; + } + } + catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public void close(AiInputStreamIOStream file) { + } + + @Override + public boolean exists(String path) + { + URL url = null; + if(clazz != null) { + url = clazz.getResource(path); + } + else if (classLoader != null) { + url = classLoader.getResource(path); + } + + + if(url == null) + { + return false; + } + else + { + return true; + } + + } + + @Override + public char getOsSeparator() + { + return '/'; + } + +} diff --git a/port/jassimp/jassimp/src/jassimp/AiIOStream.java b/port/jassimp/jassimp/src/jassimp/AiIOStream.java new file mode 100644 index 000000000..5378da5f8 --- /dev/null +++ b/port/jassimp/jassimp/src/jassimp/AiIOStream.java @@ -0,0 +1,55 @@ +/* + * Copyright 2017 Florida Institute for Human and Machine Cognition (IHMC) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package jassimp; + +import java.nio.ByteBuffer; + + +/** + * Interface to allow custom resource loaders for jassimp.

+ * + * The design is based on passing the file wholly in memory, + * because Java inputstreams do not have to support seek.

+ * + * Writing files from Java is unsupported. + * + * + * @author Jesper Smith + * + */ +public interface AiIOStream +{ + + /** + * Read all data into buffer.

+ * + * The whole stream should be read into the buffer. + * No support is provided for partial reads. + * + * @param buffer Target buffer for the model data + * + * @return true if successful, false if an error occurred. + */ + boolean read(ByteBuffer buffer); + + /** + * The total size of this stream.

+ * + * @return total size of this stream + */ + int getFileSize(); + +} diff --git a/port/jassimp/jassimp/src/jassimp/AiIOSystem.java b/port/jassimp/jassimp/src/jassimp/AiIOSystem.java new file mode 100644 index 000000000..d2c741529 --- /dev/null +++ b/port/jassimp/jassimp/src/jassimp/AiIOSystem.java @@ -0,0 +1,54 @@ +/* + * Copyright 2017 Florida Institute for Human and Machine Cognition (IHMC) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package jassimp; + +public interface AiIOSystem +{ + /** + * + * Open a new file with a given path. + * When the access to the file is finished, call close() to release all associated resources + * + * @param path Path to the file + * @param ioMode file I/O mode. Required are: "wb", "w", "wt", "rb", "r", "rt". + * + * @return AiIOStream or null if an error occurred + */ + public T open(String path, String ioMode); + + + /** + * Tests for the existence of a file at the given path. + * + * @param path path to the file + * @return true if there is a file with this path, else false. + */ + public boolean exists(String path); + + /** + * Returns the system specific directory separator.

+ * + * @return System specific directory separator + */ + public char getOsSeparator(); + + /** + * Closes the given file and releases all resources associated with it. + * + * @param file The file instance previously created by Open(). + */ + public void close(T file); +} diff --git a/port/jassimp/jassimp/src/jassimp/AiInputStreamIOStream.java b/port/jassimp/jassimp/src/jassimp/AiInputStreamIOStream.java new file mode 100644 index 000000000..998401b68 --- /dev/null +++ b/port/jassimp/jassimp/src/jassimp/AiInputStreamIOStream.java @@ -0,0 +1,102 @@ +/* + * Copyright 2017 Florida Institute for Human and Machine Cognition (IHMC) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package jassimp; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.URL; +import java.nio.ByteBuffer; + + +/** + * Implementation of AiIOStream reading from a InputStream + * + * @author Jesper Smith + * + */ +public class AiInputStreamIOStream implements AiIOStream +{ + private final ByteArrayOutputStream os = new ByteArrayOutputStream(); + + + public AiInputStreamIOStream(URI uri) throws IOException { + this(uri.toURL()); + } + + public AiInputStreamIOStream(URL url) throws IOException { + this(url.openStream()); + } + + public AiInputStreamIOStream(InputStream is) throws IOException { + int read; + byte[] data = new byte[1024]; + while((read = is.read(data, 0, data.length)) != -1) { + os.write(data, 0, read); + } + os.flush(); + + is.close(); + } + + @Override + public int getFileSize() { + return os.size(); + } + + @Override + public boolean read(ByteBuffer buffer) { + ByteBufferOutputStream bos = new ByteBufferOutputStream(buffer); + try + { + os.writeTo(bos); + } + catch (IOException e) + { + e.printStackTrace(); + return false; + } + return true; + } + + /** + * Internal helper class to copy the contents of an OutputStream + * into a ByteBuffer. This avoids a copy. + * + */ + private static class ByteBufferOutputStream extends OutputStream { + + private final ByteBuffer buffer; + + public ByteBufferOutputStream(ByteBuffer buffer) { + this.buffer = buffer; + } + + @Override + public void write(int b) throws IOException + { + buffer.put((byte) b); + } + + @Override + public void write(byte b[], int off, int len) throws IOException { + buffer.put(b, off, len); + } + } +} + diff --git a/port/jassimp/jassimp/src/jassimp/Jassimp.java b/port/jassimp/jassimp/src/jassimp/Jassimp.java index 92f4864c7..d1b4aae4e 100644 --- a/port/jassimp/jassimp/src/jassimp/Jassimp.java +++ b/port/jassimp/jassimp/src/jassimp/Jassimp.java @@ -79,6 +79,20 @@ public final class Jassimp { return importFile(filename, EnumSet.noneOf(AiPostProcessSteps.class)); } + /** + * Imports a file via assimp without post processing. + * + * @param filename the file to import + * @param ioSystem ioSystem to load files, or null for default + * @return the loaded scene + * @throws IOException if an error occurs + */ + public static AiScene importFile(String filename, AiIOSystem ioSystem) + throws IOException { + + return importFile(filename, EnumSet.noneOf(AiPostProcessSteps.class), ioSystem); + } + /** * Imports a file via assimp. @@ -89,12 +103,28 @@ public final class Jassimp { * @throws IOException if an error occurs */ public static AiScene importFile(String filename, - Set postProcessing) throws IOException { + Set postProcessing) + throws IOException { + return importFile(filename, postProcessing, null); + } + + /** + * Imports a file via assimp. + * + * @param filename the file to import + * @param postProcessing post processing flags + * @param ioSystem ioSystem to load files, or null for default + * @return the loaded scene, or null if an error occurred + * @throws IOException if an error occurs + */ + public static AiScene importFile(String filename, + Set postProcessing, AiIOSystem ioSystem) + throws IOException { loadLibrary(); return aiImportFile(filename, AiPostProcessSteps.toRawValue( - postProcessing)); + postProcessing), ioSystem); } @@ -310,7 +340,7 @@ public final class Jassimp { * @throws IOException if an error occurs */ private static native AiScene aiImportFile(String filename, - long postProcessing) throws IOException; + long postProcessing, AiIOSystem ioSystem) throws IOException; /** From ab9dda594dec379b3be09d9c7dc60fe7f53a8667 Mon Sep 17 00:00:00 2001 From: Jesper Smith Date: Fri, 28 Jul 2017 15:08:59 -0500 Subject: [PATCH 02/10] Added return statement to Write --- port/jassimp/jassimp-native/src/jassimp.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/port/jassimp/jassimp-native/src/jassimp.cpp b/port/jassimp/jassimp-native/src/jassimp.cpp index 475f6c5a0..75b1bc510 100644 --- a/port/jassimp/jassimp-native/src/jassimp.cpp +++ b/port/jassimp/jassimp-native/src/jassimp.cpp @@ -471,7 +471,10 @@ public: return cnt; }; - size_t Write(const void* pvBuffer, size_t pSize, size_t pCount) {}; + size_t Write(const void* pvBuffer, size_t pSize, size_t pCount) + { + return 0; + }; aiReturn Seek(size_t pOffset, aiOrigin pOrigin) { From 3964f5cf43ca61a8184f597f5e7df7dc29b9eeb8 Mon Sep 17 00:00:00 2001 From: Doug Stephen Date: Tue, 3 Oct 2017 14:11:23 -0500 Subject: [PATCH 03/10] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 664708a90..12cd76a87 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,5 @@ lib64/assimp-vc120-mtd.ilk lib64/assimp-vc120-mtd.exp lib64/assimp-vc120-mt.exp xcuserdata + +cmake-build-debug From 3ef1f37a809ee7876d1faee0262d0b72ae7f8315 Mon Sep 17 00:00:00 2001 From: Doug Stephen Date: Wed, 11 Oct 2017 10:54:27 -0500 Subject: [PATCH 04/10] Create AiMetadataEntry.java for jassimp port. --- .../jassimp/src/jassimp/AiMetadataEntry.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 port/jassimp/jassimp/src/jassimp/AiMetadataEntry.java diff --git a/port/jassimp/jassimp/src/jassimp/AiMetadataEntry.java b/port/jassimp/jassimp/src/jassimp/AiMetadataEntry.java new file mode 100644 index 000000000..3dd983b45 --- /dev/null +++ b/port/jassimp/jassimp/src/jassimp/AiMetadataEntry.java @@ -0,0 +1,35 @@ +package jassimp; + +/** + * @author Doug Stephen (dstephen@ihmc.us) + */ +public class AiMetadataEntry +{ + public enum AiMetadataType + { + AI_BOOL, AI_INT32, AI_UINT64, AI_FLOAT, AI_DOUBLE, AI_AISTRING, AI_AIVECTOR3D + } + + private AiMetadataType mType; + private Object mData; + + public AiMetadataType getMetaDataType() + { + return mType; + } + +// public void setMetaDataType(AiMetadataType type) +// { +// this.mType = type; +// } + + public Object getData() + { + return mData; + } + +// public void setData(Object data) +// { +// this.mData = data; +// } +} From 00eb2e401a8088af072cdcdcb2253242bf2c4386 Mon Sep 17 00:00:00 2001 From: Doug Stephen Date: Wed, 11 Oct 2017 10:56:40 -0500 Subject: [PATCH 05/10] Added field and getter for metadata entries to AiNode.java. --- port/jassimp/jassimp/src/jassimp/AiNode.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/port/jassimp/jassimp/src/jassimp/AiNode.java b/port/jassimp/jassimp/src/jassimp/AiNode.java index 5cd26b668..9345b7af4 100644 --- a/port/jassimp/jassimp/src/jassimp/AiNode.java +++ b/port/jassimp/jassimp/src/jassimp/AiNode.java @@ -41,7 +41,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package jassimp; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** @@ -185,6 +187,18 @@ public final class AiNode { public int[] getMeshes() { return m_meshReferences; } + + /** + * Returns the metadata entries for this node.

+ * + * Consult the original Doxygen for importer_notes to + * see which formats have metadata and what to expect. + * + * @return A map of metadata names to entries. + */ + public Map getMetadata() { + return m_metaData; + } /** @@ -219,6 +233,11 @@ public final class AiNode { * List of children. */ private final List m_children = new ArrayList(); + + /** + * List of metadata entries. + */ + private final Map m_metaData = new HashMap(); /** From 33a54f021e2ebc0ea199f50f203621f459ff5993 Mon Sep 17 00:00:00 2001 From: Doug Stephen Date: Wed, 11 Oct 2017 11:03:29 -0500 Subject: [PATCH 06/10] Fix small bug in getStaticField --- port/jassimp/jassimp-native/src/jassimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/port/jassimp/jassimp-native/src/jassimp.cpp b/port/jassimp/jassimp-native/src/jassimp.cpp index 75b1bc510..5dbcb22cd 100644 --- a/port/jassimp/jassimp-native/src/jassimp.cpp +++ b/port/jassimp/jassimp-native/src/jassimp.cpp @@ -214,7 +214,7 @@ static bool getStaticField(JNIEnv *env, const char* className, const char* field return false; } - jfieldID fieldId = env->GetFieldID(clazz, fieldName, signature); + jfieldID fieldId = env->GetStaticFieldID(clazz, fieldName, signature); if (NULL == fieldId) { From 5e00d4d5cba4c444b609f23fa18b60b3ca067ba6 Mon Sep 17 00:00:00 2001 From: Doug Stephen Date: Wed, 11 Oct 2017 11:07:49 -0500 Subject: [PATCH 07/10] Populate metadata on Java objects. --- port/jassimp/jassimp-native/src/jassimp.cpp | 179 +++++++++++++++++++- 1 file changed, 171 insertions(+), 8 deletions(-) diff --git a/port/jassimp/jassimp-native/src/jassimp.cpp b/port/jassimp/jassimp-native/src/jassimp.cpp index 5dbcb22cd..c2a893896 100644 --- a/port/jassimp/jassimp-native/src/jassimp.cpp +++ b/port/jassimp/jassimp-native/src/jassimp.cpp @@ -1005,8 +1005,164 @@ static bool loadMeshes(JNIEnv *env, const aiScene* cScene, jobject& jScene) return true; } +static bool loadMetadata(JNIEnv *env, const aiNode* cNode, jobject& jNode) +{ + aiMetadata *cMetadata = cNode->mMetaData; -static bool loadSceneNode(JNIEnv *env, const aiNode *cNode, jobject parent, jobject* loadedNode = NULL) + for(unsigned i = 0; imNumProperties; i++) { + + aiString& metaDataKey = cMetadata->mKeys[i]; + void* cData = cMetadata->mValues[i].mData; + aiMetadataType cMetadataType = cMetadata->mValues[i].mType; + + jobject jAiMetadataEntry = NULL; + SmartLocalRef refMetadataEntry(env, jAiMetadataEntry); + + if(!createInstance(env, "jassimp/AiMetadataEntry", jAiMetadataEntry)) { + return false; + } + + jobject jAiMetadataTypeEnumValue = NULL; + SmartLocalRef refMetadataTypeEnumValue(env, jAiMetadataTypeEnumValue); + + jobject jMetadataData = NULL; + SmartLocalRef refMetadataData(env, jMetadataData); + + bool getMetadataTypeSuccess = false; + bool getMetadataDataSuccess = false; + + jvalue boxingMethodArgument[1]; + + jboolean exceptionThrown; + + switch (cMetadataType) { + + case AI_BOOL: { + getMetadataTypeSuccess = getStaticField(env, "jassimp/AiMetadataEntry$AiMetadataType", "AI_BOOL", "Ljassimp/AiMetadataEntry$AiMetadataType;", jAiMetadataTypeEnumValue); + boxingMethodArgument[0].z = (jboolean) *static_cast(cData); + getMetadataDataSuccess = callStaticObject(env, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", boxingMethodArgument, jMetadataData); + break; + } + case AI_INT32: { + getMetadataTypeSuccess = getStaticField(env, "jassimp/AiMetadataEntry$AiMetadataType", "AI_INT32", "Ljassimp/AiMetadataEntry$AiMetadataType;", jAiMetadataTypeEnumValue); + boxingMethodArgument[0].i = (jint) *static_cast(cData); + getMetadataDataSuccess = callStaticObject(env, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", boxingMethodArgument, jMetadataData); + break; + } + case AI_UINT64: { + getMetadataTypeSuccess = getStaticField(env, "jassimp/AiMetadataEntry$AiMetadataType", "AI_UINT64", "Ljassimp/AiMetadataEntry$AiMetadataType;", jAiMetadataTypeEnumValue); + boxingMethodArgument[0].j = (jlong) *static_cast(cData); + getMetadataDataSuccess = callStaticObject(env, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", boxingMethodArgument, jMetadataData); + break; + } + case AI_FLOAT: { + getMetadataTypeSuccess = getStaticField(env, "jassimp/AiMetadataEntry$AiMetadataType", "AI_FLOAT", "Ljassimp/AiMetadataEntry$AiMetadataType;", jAiMetadataTypeEnumValue); + boxingMethodArgument[0].f = (jfloat) *static_cast(cData); + getMetadataDataSuccess = callStaticObject(env, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", boxingMethodArgument, jMetadataData); + break; + } + case AI_DOUBLE: { + getMetadataTypeSuccess = getStaticField(env, "jassimp/AiMetadataEntry$AiMetadataType", "AI_DOUBLE", "Ljassimp/AiMetadataEntry$AiMetadataType;", jAiMetadataTypeEnumValue); + boxingMethodArgument[0].d = (jdouble) *static_cast(cData); + getMetadataDataSuccess = callStaticObject(env, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", boxingMethodArgument, jMetadataData); + break; + } + case AI_AISTRING: { + getMetadataTypeSuccess = getStaticField(env, "jassimp/AiMetadataEntry$AiMetadataType", "AI_AISTRING", "Ljassimp/AiMetadataEntry$AiMetadataType;", jAiMetadataTypeEnumValue); + jMetadataData = env->NewStringUTF(static_cast(cData)->C_Str()); + getMetadataDataSuccess = (jMetadataData != NULL); + break; + } + case AI_AIVECTOR3D: { + getMetadataTypeSuccess = getStaticField(env, "jassimp/AiMetadataEntry$AiMetadataType", "AI_AIVECTOR3D", + "Ljassimp/AiMetadataEntry$AiMetadataType;", + jAiMetadataTypeEnumValue); + jvalue wrapVec3Args[3]; + aiVector3D *vector3D = static_cast(cData); + wrapVec3Args[0].f = vector3D->x; + wrapVec3Args[1].f = vector3D->y; + wrapVec3Args[2].f = vector3D->z; + getMetadataDataSuccess = callStaticObject(env, "jassimp/Jassimp", "wrapVec3", "(FFF)Ljava/lang/Object;", + wrapVec3Args, jMetadataData); + break; + } + default: { + getMetadataTypeSuccess = false; + getMetadataDataSuccess = false; + break; + } + } + + exceptionThrown = env->ExceptionCheck(); + + if(!getMetadataTypeSuccess || !getMetadataDataSuccess) { + if(exceptionThrown) + { + env->ExceptionDescribe(); + } + + return false; + } + + if(!setObjectField(env, jAiMetadataEntry, "mType", "Ljassimp/AiMetadataEntry$AiMetadataType;", jAiMetadataTypeEnumValue)) { + exceptionThrown = env->ExceptionCheck(); + + if(exceptionThrown) + { + env->ExceptionDescribe(); + } + + return false; + } + + if(!setObjectField(env, jAiMetadataEntry, "mData", "Ljava/lang/Object;", jMetadataData)) { + exceptionThrown = env->ExceptionCheck(); + + if(exceptionThrown) + { + env->ExceptionDescribe(); + } + + return false; + } + + jobject jNodeMetadata = NULL; + SmartLocalRef refMetadata(env, jNodeMetadata); + + if(!getField(env, jNode, "m_metaData", "Ljava/util/Map;", jNodeMetadata)) { + exceptionThrown = env->ExceptionCheck(); + + if(exceptionThrown) + { + env->ExceptionDescribe(); + } + + return false; + } + + jclass hashMapClass = env->FindClass("java/util/HashMap"); + jmethodID jHashMapPutMethod = env->GetMethodID(hashMapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + + jstring jKey = env->NewStringUTF(metaDataKey.C_Str()); + SmartLocalRef keyRef(env, jKey); + + // Only check exception instead of result here because maps will return + // null on success if they did not overwrite an existing mapping for the given key. + env->CallObjectMethod(jNodeMetadata, jHashMapPutMethod, jKey, jAiMetadataEntry); + + exceptionThrown = env->ExceptionCheck(); + + if(exceptionThrown) { + env->ExceptionDescribe(); + return false; + } + + } + + return true; +} + +static bool loadSceneNode(JNIEnv *env, const aiNode *cNode, jobject parent, jobject* loadedNode = NULL) { lprintf(" converting node %s ...\n", cNode->mName.C_Str()); @@ -1019,7 +1175,7 @@ static bool loadSceneNode(JNIEnv *env, const aiNode *cNode, jobject parent, jobj wrapMatParams[0].l = jMatrixArr; jobject jMatrix; SmartLocalRef refMatrix(env, jMatrix); - + if (!callStaticObject(env, "jassimp/Jassimp", "wrapMatrix", "([F)Ljava/lang/Object;", wrapMatParams, jMatrix)) { return false; @@ -1068,12 +1224,19 @@ static bool loadSceneNode(JNIEnv *env, const aiNode *cNode, jobject parent, jobj } } - if (NULL != loadedNode) - { - *loadedNode = jNode; - } else { - env->DeleteLocalRef(jNode); - } + if (NULL != loadedNode) + { + if(cNode->mMetaData) { + if(!loadMetadata(env, cNode, jNode)) + { + return false; + } + } + + *loadedNode = jNode; + } else { + env->DeleteLocalRef(jNode); + } return true; } From ff758e4c155770a866405e4802b5e84aab97251c Mon Sep 17 00:00:00 2001 From: Alexandre Avenel Date: Sun, 15 Oct 2017 19:53:26 +0200 Subject: [PATCH 08/10] OBJ : add unit test to validate relative indices --- test/unit/utObjImportExport.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/unit/utObjImportExport.cpp b/test/unit/utObjImportExport.cpp index 4aafa3dc2..3b08df80f 100644 --- a/test/unit/utObjImportExport.cpp +++ b/test/unit/utObjImportExport.cpp @@ -280,3 +280,28 @@ TEST_F( utObjImportExport, issue1453_segfault ) { const aiScene *scene = myimporter.ReadFileFromMemory( ObjModel.c_str(), ObjModel.size(), aiProcess_ValidateDataStructure ); EXPECT_EQ( nullptr, scene ); } + +TEST_F(utObjImportExport, relative_indices_Test) { + static const std::string ObjModel = + "v -0.500000 0.000000 0.400000\n" + "v -0.500000 0.000000 -0.800000\n" + "v -0.500000 1.000000 -0.800000\n" + "v -0.500000 1.000000 0.400000\n" + "f -4 -3 -2 -1\nB"; + + Assimp::Importer myimporter; + const aiScene *scene = myimporter.ReadFileFromMemory(ObjModel.c_str(), ObjModel.size(), aiProcess_ValidateDataStructure); + EXPECT_NE(nullptr, scene); + + EXPECT_EQ(scene->mNumMeshes, 1); + const aiMesh *mesh = scene->mMeshes[0]; + EXPECT_EQ(mesh->mNumVertices, 4); + EXPECT_EQ(mesh->mNumFaces, 1); + const aiFace face = mesh->mFaces[0]; + EXPECT_EQ(face.mNumIndices, 4); + for (unsigned int i = 0; i < face.mNumIndices; ++i) + { + EXPECT_EQ(face.mIndices[i], i); + } + +} From 1aa15c806955749311ec5e8cffc39ff20dd86282 Mon Sep 17 00:00:00 2001 From: Doug Stephen Date: Tue, 17 Oct 2017 10:35:50 -0500 Subject: [PATCH 09/10] Fix header and remove old debug code --- .../jassimp/src/jassimp/AiMetadataEntry.java | 52 ++++++++++++++----- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/port/jassimp/jassimp/src/jassimp/AiMetadataEntry.java b/port/jassimp/jassimp/src/jassimp/AiMetadataEntry.java index 3dd983b45..615572e79 100644 --- a/port/jassimp/jassimp/src/jassimp/AiMetadataEntry.java +++ b/port/jassimp/jassimp/src/jassimp/AiMetadataEntry.java @@ -1,8 +1,44 @@ package jassimp; +/* +--------------------------------------------------------------------------- +Open Asset Import Library - Java Binding (jassimp) +--------------------------------------------------------------------------- -/** - * @author Doug Stephen (dstephen@ihmc.us) - */ +Copyright (c) 2006-2012, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ public class AiMetadataEntry { public enum AiMetadataType @@ -18,18 +54,8 @@ public class AiMetadataEntry return mType; } -// public void setMetaDataType(AiMetadataType type) -// { -// this.mType = type; -// } - public Object getData() { return mData; } - -// public void setData(Object data) -// { -// this.mData = data; -// } } From 42e2c30b4b5cff63eae028a0e889a9ae7a831180 Mon Sep 17 00:00:00 2001 From: Doug Stephen Date: Tue, 17 Oct 2017 11:04:51 -0500 Subject: [PATCH 10/10] Added helper getters for casting metadata payloads --- .../jassimp/src/jassimp/AiMetadataEntry.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/port/jassimp/jassimp/src/jassimp/AiMetadataEntry.java b/port/jassimp/jassimp/src/jassimp/AiMetadataEntry.java index 615572e79..dbdf1aae8 100644 --- a/port/jassimp/jassimp/src/jassimp/AiMetadataEntry.java +++ b/port/jassimp/jassimp/src/jassimp/AiMetadataEntry.java @@ -58,4 +58,61 @@ public class AiMetadataEntry { return mData; } + + public static boolean getAiBoolAsBoolean(AiMetadataEntry metadataEntry) + { + checkTypeBeforeCasting(metadataEntry, AiMetadataType.AI_BOOL); + + return (boolean) metadataEntry.mData; + } + + public static int getAiInt32AsInteger(AiMetadataEntry metadataEntry) + { + checkTypeBeforeCasting(metadataEntry, AiMetadataType.AI_INT32); + + return (int) metadataEntry.mData; + } + + public static long getAiUint64AsLong(AiMetadataEntry metadataEntry) + { + checkTypeBeforeCasting(metadataEntry, AiMetadataType.AI_UINT64); + + return (long) metadataEntry.mData; + } + + public static float getAiFloatAsFloat(AiMetadataEntry metadataEntry) + { + checkTypeBeforeCasting(metadataEntry, AiMetadataType.AI_FLOAT); + + return (float) metadataEntry.mData; + } + + public static double getAiDoubleAsDouble(AiMetadataEntry metadataEntry) + { + checkTypeBeforeCasting(metadataEntry, AiMetadataType.AI_DOUBLE); + + return (double) metadataEntry.mData; + } + + public static String getAiStringAsString(AiMetadataEntry metadataEntry) + { + checkTypeBeforeCasting(metadataEntry, AiMetadataType.AI_AISTRING); + + return (String) metadataEntry.mData; + } + + public static AiVector getAiAiVector3DAsAiVector(AiMetadataEntry metadataEntry) + { + checkTypeBeforeCasting(metadataEntry, AiMetadataType.AI_AIVECTOR3D); + + return (AiVector) metadataEntry.mData; + } + + private static void checkTypeBeforeCasting(AiMetadataEntry entry, AiMetadataType expectedType) + { + if(entry.mType != expectedType) + { + throw new RuntimeException("Cannot cast entry of type " + entry.mType.name() + " to " + expectedType.name()); + } + } }