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 diff --git a/port/jassimp/jassimp-native/src/jassimp.cpp b/port/jassimp/jassimp-native/src/jassimp.cpp index 75b1bc510..c2a893896 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) { @@ -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; } diff --git a/port/jassimp/jassimp/src/jassimp/AiMetadataEntry.java b/port/jassimp/jassimp/src/jassimp/AiMetadataEntry.java new file mode 100644 index 000000000..dbdf1aae8 --- /dev/null +++ b/port/jassimp/jassimp/src/jassimp/AiMetadataEntry.java @@ -0,0 +1,118 @@ +package jassimp; +/* +--------------------------------------------------------------------------- +Open Asset Import Library - Java Binding (jassimp) +--------------------------------------------------------------------------- + +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 + { + 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 Object getData() + { + 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()); + } + } +} 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(); /** 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); + } + +}