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; /**