#include "jassimp.h"
-#include <assimp/cimport.h>
+#include <assimp/Importer.hpp>
#include <assimp/scene.h>
+#include <assimp/IOStream.hpp>
+#include <assimp/IOSystem.hpp>
#ifdef JNI_LOG
#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:
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)
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)
JNIEXPORT jstring JNICALL Java_jassimp_Jassimp_getErrorString
(JNIEnv *env, jclass jClazz)
{
- const char *err = aiGetErrorString();
+ const char *err = gLastErrorString.c_str();
if (NULL == err)
{
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)
{
/* 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);
* 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
}
--- /dev/null
+/*
+ * 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.<p>
+ *
+ * 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<AiInputStreamIOStream>
+{
+ private final Class<?> clazz;
+ private final ClassLoader classLoader;
+
+ /**
+ * Construct a new AiClassLoaderIOSystem.<p>
+ *
+ * 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.<p>
+ *
+ * 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 '/';
+ }
+
+}
--- /dev/null
+/*
+ * 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.<p>
+ *
+ * The design is based on passing the file wholly in memory,
+ * because Java inputstreams do not have to support seek. <p>
+ *
+ * Writing files from Java is unsupported.
+ *
+ *
+ * @author Jesper Smith
+ *
+ */
+public interface AiIOStream
+{
+
+ /**
+ * Read all data into buffer. <p>
+ *
+ * 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. <p>
+ *
+ * @return total size of this stream
+ */
+ int getFileSize();
+
+}
--- /dev/null
+/*
+ * 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 <T extends AiIOStream>
+{
+ /**
+ *
+ * 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.<p>
+ *
+ * @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);
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
+
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.
+ *
+ * @param filename the file to import
+ * @param postProcessing post processing flags
+ * @return the loaded scene, or null if an error occurred
+ * @throws IOException if an error occurs
+ */
+ public static AiScene importFile(String filename,
+ Set<AiPostProcessSteps> 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<AiPostProcessSteps> postProcessing) throws IOException {
+ Set<AiPostProcessSteps> postProcessing, AiIOSystem<?> ioSystem)
+ throws IOException {
loadLibrary();
return aiImportFile(filename, AiPostProcessSteps.toRawValue(
- postProcessing));
+ postProcessing), ioSystem);
}
* @throws IOException if an error occurs
*/
private static native AiScene aiImportFile(String filename,
- long postProcessing) throws IOException;
+ long postProcessing, AiIOSystem<?> ioSystem) throws IOException;
/**