Explorar o código

Merge branch 'release/1.10.x'

rdb %!s(int64=3) %!d(string=hai) anos
pai
achega
e75aae62f0

+ 23 - 8
panda/src/gobj/textureStage.cxx

@@ -15,6 +15,7 @@
 #include "internalName.h"
 #include "bamReader.h"
 #include "bamWriter.h"
+#include "indent.h"
 
 using std::ostream;
 
@@ -221,15 +222,28 @@ compare_to(const TextureStage &other) const {
  */
 void TextureStage::
 write(ostream &out) const {
-  out << "TextureStage " << get_name() << ", sort = " << get_sort() << ", priority = " << get_priority() << "\n"
-      << "  texcoords = " << get_texcoord_name()->get_name()
-      << ", mode = " << get_mode() << ", color = " << get_color()
-      << ", scale = " << get_rgb_scale() << ", " << get_alpha_scale()
-      << ", saved_result = " << get_saved_result()
-      << ", tex_view_offset = " << get_tex_view_offset() << "\n";
+  write(out, 0);
+}
+
+/**
+ * Writes the details of this stage
+ */
+void TextureStage::
+write(ostream &out, int indent_level) const {
+  indent(out, indent_level)
+    << "TextureStage " << get_name() << ", sort = " << get_sort()
+    << ", priority = " << get_priority() << "\n";
+
+  indent(out, indent_level)
+    << "  texcoords = " << get_texcoord_name()->get_name()
+    << ", mode = " << get_mode() << ", color = " << get_color()
+    << ", scale = " << get_rgb_scale() << ", " << get_alpha_scale()
+    << ", saved_result = " << get_saved_result()
+    << ", tex_view_offset = " << get_tex_view_offset() << "\n";
 
   if (get_mode() == M_combine) {
-    out << "  RGB combine mode =  " << get_combine_rgb_mode() << "\n";
+    indent(out, indent_level)
+      << "  RGB combine mode =  " << get_combine_rgb_mode() << "\n";
     if (get_num_combine_rgb_operands() >= 1) {
       out << "    0: " << get_combine_rgb_source0() << ", "
           << get_combine_rgb_operand0() << "\n";
@@ -242,7 +256,8 @@ write(ostream &out) const {
       out << "    2: " << get_combine_rgb_source2() << ", "
           << get_combine_rgb_operand2() << "\n";
     }
-    out << "  alpha combine mode =  " << get_combine_alpha_mode() << "\n";
+    indent(out, indent_level)
+      << "  alpha combine mode =  " << get_combine_alpha_mode() << "\n";
     if (get_num_combine_alpha_operands() >= 1) {
       out << "    0: " << get_combine_alpha_source0() << ", "
           << get_combine_alpha_operand0() << "\n";

+ 1 - 0
panda/src/gobj/textureStage.h

@@ -182,6 +182,7 @@ PUBLISHED:
   int compare_to(const TextureStage &other) const;
 
   void write(std::ostream &out) const;
+  void write(std::ostream &out, int indent_level) const;
   void output(std::ostream &out) const;
 
   INLINE static TextureStage *get_default();

+ 270 - 24
pandatool/src/assimp/assimpLoader.cxx

@@ -21,9 +21,11 @@
 #include "geomTriangles.h"
 #include "pnmFileTypeRegistry.h"
 #include "pnmImage.h"
+#include "alphaTestAttrib.h"
 #include "materialAttrib.h"
 #include "textureAttrib.h"
 #include "cullFaceAttrib.h"
+#include "transparencyAttrib.h"
 #include "ambientLight.h"
 #include "directionalLight.h"
 #include "spotlight.h"
@@ -35,12 +37,48 @@
 #include "animBundleNode.h"
 #include "animChannelMatrixXfmTable.h"
 #include "pvector.h"
+#include "cmath.h"
+#include "deg_2_rad.h"
+#include "string_utils.h"
 
 #include "pandaIOSystem.h"
 #include "pandaLogger.h"
 
 #include <assimp/postprocess.h>
 
+#ifndef AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR
+#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR "$mat.gltf.pbrMetallicRoughness.baseColorFactor", 0, 0
+#endif
+
+#ifndef AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR
+#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR "$mat.gltf.pbrMetallicRoughness.metallicFactor", 0, 0
+#endif
+
+#ifndef AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR
+#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR "$mat.gltf.pbrMetallicRoughness.roughnessFactor", 0, 0
+#endif
+
+#ifndef AI_MATKEY_GLTF_ALPHAMODE
+#define AI_MATKEY_GLTF_ALPHAMODE "$mat.gltf.alphaMode", 0, 0
+#endif
+
+#ifndef AI_MATKEY_GLTF_ALPHACUTOFF
+#define AI_MATKEY_GLTF_ALPHACUTOFF "$mat.gltf.alphaCutoff", 0, 0
+#endif
+
+// Older versions of Assimp used these glTF-specific keys instead.
+#ifndef AI_MATKEY_BASE_COLOR
+#define AI_MATKEY_BASE_COLOR AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR
+#endif
+
+#ifndef AI_MATKEY_METALLIC_FACTOR
+#define AI_MATKEY_METALLIC_FACTOR AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR
+#endif
+
+#ifndef AI_MATKEY_ROUGHNESS_FACTOR
+#define AI_MATKEY_ROUGHNESS_FACTOR AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR
+#endif
+
 using std::ostringstream;
 using std::stringstream;
 using std::string;
@@ -265,7 +303,8 @@ load_texture(size_t index) {
   } else {
     if (assimp_cat.is_debug()) {
       assimp_cat.debug()
-        << "Reading embedded raw texture with size " << tex.mWidth << "x" << tex.mHeight << "\n";
+        << "Reading embedded raw texture with size "
+        << tex.mWidth << "x" << tex.mHeight << "\n";
     }
 
     ptex->setup_2d_texture(tex.mWidth, tex.mHeight, Texture::T_unsigned_byte, Texture::F_rgba);
@@ -292,16 +331,18 @@ load_texture(size_t index) {
  * Converts an aiMaterial into a RenderState.
  */
 void AssimpLoader::
-load_texture_stage(const aiMaterial &mat, const aiTextureType &ttype, CPT(TextureAttrib) &tattr) {
+load_texture_stage(const aiMaterial &mat, const aiTextureType &ttype,
+                   TextureStage::Mode mode, CPT(TextureAttrib) &tattr,
+                   CPT(TexMatrixAttrib) &tmattr) {
   aiString path;
   aiTextureMapping mapping;
   unsigned int uvindex;
   float blend;
   aiTextureOp op;
-  aiTextureMapMode mapmode;
+  aiTextureMapMode mapmode[3];
 
   for (size_t i = 0; i < mat.GetTextureCount(ttype); ++i) {
-    mat.GetTexture(ttype, i, &path, &mapping, nullptr, &blend, &op, &mapmode);
+    mat.GetTexture(ttype, i, &path, &mapping, nullptr, &blend, &op, mapmode);
 
     if (AI_SUCCESS != mat.Get(AI_MATKEY_UVWSRC(ttype, i), uvindex)) {
       // If there's no texture coordinate set for this texture, assume that
@@ -310,13 +351,23 @@ load_texture_stage(const aiMaterial &mat, const aiTextureType &ttype, CPT(Textur
       uvindex = i;
     }
 
-    stringstream str;
-    str << uvindex;
-    PT(TextureStage) stage = new TextureStage(str.str());
+    if (ttype == aiTextureType_DIFFUSE && i == 1) {
+      // The glTF 2 importer duplicates this slot in older versions of Assimp.
+      // Since glTF doesn't support multiple diffuse textures anyway, we check
+      // for this old glTF-specific key, and if present, ignore this texture.
+      aiColor4D col;
+      if (AI_SUCCESS == mat.Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR, col)) {
+        return;
+      }
+    }
+
+    std::string uvindex_str = format_string(uvindex);
+    PT(TextureStage) stage = new TextureStage(uvindex_str);
+    stage->set_mode(mode);
     if (uvindex > 0) {
-      stage->set_texcoord_name(InternalName::get_texcoord_name(str.str()));
+      stage->set_texcoord_name(InternalName::get_texcoord_name(uvindex_str));
     }
-    PT(Texture) ptex = nullptr;
+    PT(Texture) ptex;
 
     // I'm not sure if this is the right way to handle it, as I couldn't find
     // much information on embedded textures.
@@ -355,7 +406,84 @@ load_texture_stage(const aiMaterial &mat, const aiTextureType &ttype, CPT(Textur
     }
 
     if (ptex != nullptr) {
+      // Apply the mapping modes.
+      switch (mapmode[0]) {
+      case aiTextureMapMode_Wrap:
+        ptex->set_wrap_u(SamplerState::WM_repeat);
+        break;
+      case aiTextureMapMode_Clamp:
+        ptex->set_wrap_u(SamplerState::WM_clamp);
+        break;
+      case aiTextureMapMode_Decal:
+        ptex->set_wrap_u(SamplerState::WM_border_color);
+        ptex->set_border_color(LColor(0, 0, 0, 0));
+        break;
+      case aiTextureMapMode_Mirror:
+        ptex->set_wrap_u(SamplerState::WM_mirror);
+        break;
+      default:
+        break;
+      }
+      switch (mapmode[1]) {
+      case aiTextureMapMode_Wrap:
+        ptex->set_wrap_v(SamplerState::WM_repeat);
+        break;
+      case aiTextureMapMode_Clamp:
+        ptex->set_wrap_v(SamplerState::WM_clamp);
+        break;
+      case aiTextureMapMode_Decal:
+        ptex->set_wrap_v(SamplerState::WM_border_color);
+        ptex->set_border_color(LColor(0, 0, 0, 0));
+        break;
+      case aiTextureMapMode_Mirror:
+        ptex->set_wrap_v(SamplerState::WM_mirror);
+        break;
+      default:
+        break;
+      }
+      switch (mapmode[2]) {
+      case aiTextureMapMode_Wrap:
+        ptex->set_wrap_w(SamplerState::WM_repeat);
+        break;
+      case aiTextureMapMode_Clamp:
+        ptex->set_wrap_w(SamplerState::WM_clamp);
+        break;
+      case aiTextureMapMode_Decal:
+        ptex->set_wrap_w(SamplerState::WM_border_color);
+        ptex->set_border_color(LColor(0, 0, 0, 0));
+        break;
+      case aiTextureMapMode_Mirror:
+        ptex->set_wrap_w(SamplerState::WM_mirror);
+        break;
+      default:
+        break;
+      }
+
       tattr = DCAST(TextureAttrib, tattr->add_on_stage(stage, ptex));
+
+      // Is there a texture transform?
+      aiUVTransform transform;
+      if (AI_SUCCESS == mat.Get(AI_MATKEY_UVTRANSFORM(ttype, i), transform)) {
+        // Reconstruct the original origin from the glTF file.
+        PN_stdfloat rcos, rsin;
+        csincos(-transform.mRotation, &rsin, &rcos);
+        transform.mTranslation.x -= (0.5 * transform.mScaling.x) * (-rcos + rsin + 1);
+        transform.mTranslation.y -= ((0.5 * transform.mScaling.y) * (rsin + rcos - 1)) + 1 - transform.mScaling.y;
+
+        LMatrix3 matrix =
+          LMatrix3::translate_mat(0, -1) *
+          LMatrix3::scale_mat(transform.mScaling.x, transform.mScaling.y) *
+          LMatrix3::rotate_mat(rad_2_deg(-transform.mRotation)) *
+          LMatrix3::translate_mat(transform.mTranslation.x, 1 + transform.mTranslation.y);
+
+        CPT(TransformState) cstate =
+          TransformState::make_mat3(matrix);
+
+        CPT(RenderAttrib) new_attr = (tmattr == nullptr)
+          ? TexMatrixAttrib::make(stage, std::move(cstate))
+          : tmattr->add_stage(stage, std::move(cstate));
+        tmattr = DCAST(TexMatrixAttrib, std::move(new_attr));
+      }
     }
   }
 }
@@ -369,7 +497,7 @@ load_material(size_t index) {
 
   CPT(RenderState) state = RenderState::make_empty();
 
-  aiColor3D col;
+  aiColor4D col;
   bool have;
   int ival;
   PN_stdfloat fval;
@@ -379,7 +507,11 @@ load_material(size_t index) {
   // First do the material attribute.
   PT(Material) pmat = new Material;
   have = false;
-  if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_DIFFUSE, col)) {
+  if (AI_SUCCESS == mat.Get(AI_MATKEY_BASE_COLOR, col)) {
+    pmat->set_base_color(LColor(col.r, col.g, col.b, col.a));
+    have = true;
+  }
+  else if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_DIFFUSE, col)) {
     pmat->set_diffuse(LColor(col.r, col.g, col.b, 1));
     have = true;
   }
@@ -391,6 +523,13 @@ load_material(size_t index) {
     }
     have = true;
   }
+  //else {
+  //  if (AI_SUCCESS == mat.Get(AI_MATKEY_SHININESS_STRENGTH, fval)) {
+  //    pmat->set_specular(LColor(fval, fval, fval, 1));
+  //  } else {
+  //    pmat->set_specular(LColor(1, 1, 1, 1));
+  //  }
+  //}
   if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_AMBIENT, col)) {
     pmat->set_specular(LColor(col.r, col.g, col.b, 1));
     have = true;
@@ -406,6 +545,22 @@ load_material(size_t index) {
     pmat->set_shininess(fval);
     have = true;
   }
+  if (AI_SUCCESS == mat.Get(AI_MATKEY_METALLIC_FACTOR, fval)) {
+    pmat->set_metallic(fval);
+    have = true;
+  }
+  if (AI_SUCCESS == mat.Get(AI_MATKEY_ROUGHNESS_FACTOR, fval)) {
+    pmat->set_roughness(fval);
+    have = true;
+  }
+  if (AI_SUCCESS == mat.Get(AI_MATKEY_REFRACTI, fval)) {
+    pmat->set_refractive_index(fval);
+    have = true;
+  }
+  else if (pmat->has_metallic()) {
+    // Default refractive index to 1.5 for PBR models
+    pmat->set_refractive_index(1.5);
+  }
   if (have) {
     state = state->add_attrib(MaterialAttrib::make(pmat));
   }
@@ -429,15 +584,44 @@ load_material(size_t index) {
     }
   }
 
+  // Alpha mode.
+  aiString alpha_mode;
+  if (AI_SUCCESS == mat.Get(AI_MATKEY_GLTF_ALPHAMODE, alpha_mode)) {
+    if (strcmp(alpha_mode.C_Str(), "MASK") == 0) {
+      PN_stdfloat cutoff = 0.5;
+      mat.Get(AI_MATKEY_GLTF_ALPHACUTOFF, cutoff);
+      state = state->add_attrib(AlphaTestAttrib::make(AlphaTestAttrib::M_greater_equal, cutoff));
+    }
+    else if (strcmp(alpha_mode.C_Str(), "BLEND") == 0) {
+      state = state->add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
+    }
+  }
+
   // And let's not forget the textures!
   CPT(TextureAttrib) tattr = DCAST(TextureAttrib, TextureAttrib::make());
-  load_texture_stage(mat, aiTextureType_DIFFUSE, tattr);
-  load_texture_stage(mat, aiTextureType_LIGHTMAP, tattr);
+  CPT(TexMatrixAttrib) tmattr;
+  load_texture_stage(mat, aiTextureType_DIFFUSE, TextureStage::M_modulate, tattr, tmattr);
+
+  // Check for an ORM map, from the glTF/OBJ importer.  glTF also puts it in the
+  // LIGHTMAP slot, despite only having the lightmap in the red channel, so we
+  // have to ignore it.
+  if (mat.GetTextureCount(aiTextureType_UNKNOWN) > 0) {
+    load_texture_stage(mat, aiTextureType_UNKNOWN, TextureStage::M_selector, tattr, tmattr);
+  } else {
+    load_texture_stage(mat, aiTextureType_LIGHTMAP, TextureStage::M_modulate, tattr, tmattr);
+  }
+
+  load_texture_stage(mat, aiTextureType_NORMALS, TextureStage::M_normal, tattr, tmattr);
+  load_texture_stage(mat, aiTextureType_EMISSIVE, TextureStage::M_emission, tattr, tmattr);
+  load_texture_stage(mat, aiTextureType_HEIGHT, TextureStage::M_height, tattr, tmattr);
   if (tattr->get_num_on_stages() > 0) {
     state = state->add_attrib(tattr);
   }
+  if (tmattr != nullptr) {
+    state = state->add_attrib(tmattr);
+  }
 
-  _mat_states[index] = state;
+  _mat_states[index] = std::move(state);
 }
 
 /**
@@ -634,6 +818,10 @@ load_mesh(size_t index) {
       aformat->add_column(InternalName::get_texcoord_name(out.str()), 3, Geom::NT_stdfloat, Geom::C_texcoord);
     }
   }
+  if (mesh.HasTangentsAndBitangents()) {
+    aformat->add_column(InternalName::get_tangent(), 3, Geom::NT_stdfloat, Geom::C_vector);
+    aformat->add_column(InternalName::get_binormal(), 3, Geom::NT_stdfloat, Geom::C_vector);
+  }
 
   PT(GeomVertexArrayFormat) tb_aformat = new GeomVertexArrayFormat;
   tb_aformat->add_column(InternalName::make("transform_blend"), 1, Geom::NT_uint16, Geom::C_index);
@@ -737,7 +925,7 @@ load_mesh(size_t index) {
   GeomVertexWriter vertex (vdata, InternalName::get_vertex());
   for (size_t i = 0; i < mesh.mNumVertices; ++i) {
     const aiVector3D &vec = mesh.mVertices[i];
-    vertex.add_data3(vec.x, vec.y, vec.z);
+    vertex.set_data3(vec.x, vec.y, vec.z);
   }
 
   // Now the normals, if any.
@@ -745,7 +933,7 @@ load_mesh(size_t index) {
     GeomVertexWriter normal (vdata, InternalName::get_normal());
     for (size_t i = 0; i < mesh.mNumVertices; ++i) {
       const aiVector3D &vec = mesh.mNormals[i];
-      normal.add_data3(vec.x, vec.y, vec.z);
+      normal.set_data3(vec.x, vec.y, vec.z);
     }
   }
 
@@ -754,7 +942,7 @@ load_mesh(size_t index) {
     GeomVertexWriter color (vdata, InternalName::get_color());
     for (size_t i = 0; i < mesh.mNumVertices; ++i) {
       const aiColor4D &col = mesh.mColors[0][i];
-      color.add_data4(col.r, col.g, col.b, col.a);
+      color.set_data4(col.r, col.g, col.b, col.a);
     }
   }
 
@@ -764,7 +952,7 @@ load_mesh(size_t index) {
     GeomVertexWriter texcoord0 (vdata, InternalName::get_texcoord());
     for (size_t i = 0; i < mesh.mNumVertices; ++i) {
       const aiVector3D &vec = mesh.mTextureCoords[0][i];
-      texcoord0.add_data3(vec.x, vec.y, vec.z);
+      texcoord0.set_data3(vec.x, vec.y, vec.z);
     }
     for (unsigned int u = 1; u < num_uvs; ++u) {
       ostringstream out;
@@ -772,11 +960,23 @@ load_mesh(size_t index) {
       GeomVertexWriter texcoord (vdata, InternalName::get_texcoord_name(out.str()));
       for (size_t i = 0; i < mesh.mNumVertices; ++i) {
         const aiVector3D &vec = mesh.mTextureCoords[u][i];
-        texcoord.add_data3(vec.x, vec.y, vec.z);
+        texcoord.set_data3(vec.x, vec.y, vec.z);
       }
     }
   }
 
+  // Now the tangents and bitangents, if any.
+  if (mesh.HasTangentsAndBitangents()) {
+    GeomVertexWriter tangent (vdata, InternalName::get_tangent());
+    GeomVertexWriter binormal (vdata, InternalName::get_binormal());
+    for (size_t i = 0; i < mesh.mNumVertices; ++i) {
+      const aiVector3D &tvec = mesh.mTangents[i];
+      const aiVector3D &bvec = mesh.mBitangents[i];
+      tangent.set_data3(tvec.x, tvec.y, tvec.z);
+      binormal.set_data3(bvec.x, bvec.y, bvec.z);
+    }
+  }
+
   // Now the transform blend table
   if (character) {
     GeomVertexWriter transform_blend (vdata, InternalName::get_transform_blend());
@@ -787,7 +987,7 @@ load_mesh(size_t index) {
       for (size_t j = 0; j < bone_weights[i].size(); ++j) {
         tblend.add_transform(bone_weights[i][j].joint_vertex_xform, bone_weights[i][j].weight);
       }
-      transform_blend.add_data1i(tbtable->add_blend(tblend));
+      transform_blend.set_data1i(tbtable->add_blend(tblend));
     }
 
     tbtable->set_rows(SparseArray::lower_on(vdata->get_num_rows()));
@@ -868,10 +1068,16 @@ load_node(const aiNode &node, PandaNode *parent, bool under_joint) {
   bool prune = false;
 
   if (node.mNumMeshes == 0) {
-    pnode = new PandaNode(name);
+    if (parent == _root && assimp_collapse_dummy_root_node && !under_joint &&
+        (name.empty() || name[0] == '$' || name == "RootNode" || name == "ROOT" || name == "Root" || (name.size() > 2 && name[0] == '<' && name[name.size() - 1] == '>') || name == _root->get_name())) {
+      // Collapse root node.
+      pnode = _root;
+    } else {
+      pnode = new PandaNode(name);
 
-    // Possibly prune this if this is a joint or under a joint.
-    prune = under_joint;
+      // Possibly prune this if this is a joint or under a joint.
+      prune = under_joint;
+    }
   }
   else if (node.mNumMeshes == 1) {
     size_t meshIndex = node.mMeshes[0];
@@ -933,7 +1139,47 @@ load_node(const aiNode &node, PandaNode *parent, bool under_joint) {
     }
   }
 
-  parent->add_child(pnode);
+  if (parent != pnode) {
+    parent->add_child(pnode);
+  }
+
+  if (node.mMetaData != nullptr) {
+    for (unsigned i = 0; i < node.mMetaData->mNumProperties; ++i) {
+      const aiMetadataEntry &entry = node.mMetaData->mValues[i];
+      std::string value;
+      switch (entry.mType) {
+      //case AI_BOOL:
+      //  value = (*static_cast<bool *>(entry.mData)) ? "1" : "";
+      //  break;
+      case (aiMetadataType)1: // AI_INT32
+        value = format_string(*static_cast<int32_t *>(entry.mData));
+        break;
+      case AI_UINT64:
+        value = format_string(*static_cast<uint64_t *>(entry.mData));
+        break;
+      case AI_FLOAT:
+        value = format_string(*static_cast<float *>(entry.mData));
+        break;
+      case AI_AISTRING:
+        {
+          const aiString *str = static_cast<const aiString *>(entry.mData);
+          value = std::string(str->data, str->length);
+        }
+        break;
+      default:
+        // Special case because AI_DOUBLE was added in Assimp 4.0 with the same
+        // value as AI_AISTRING. Defined as if so that we don't get a duplicate
+        // case error with moder ncompilers.
+        if (entry.mType == (aiMetadataType)4) {
+          value = format_string(*static_cast<double *>(entry.mData));
+          break;
+        }
+        continue;
+      }
+      const aiString &key = node.mMetaData->mKeys[i];
+      pnode->set_tag(std::string(key.data, key.length), std::move(value));
+    }
+  }
 
   // Load in the transformation matrix.
   const aiMatrix4x4 &t = node.mTransformation;

+ 4 - 1
pandatool/src/assimp/assimpLoader.h

@@ -18,6 +18,7 @@
 #include "filename.h"
 #include "modelRoot.h"
 #include "texture.h"
+#include "textureStage.h"
 #include "pmap.h"
 
 #include <assimp/scene.h>
@@ -71,7 +72,9 @@ private:
   const aiNode *find_node(const aiNode &root, const aiString &name);
 
   void load_texture(size_t index);
-  void load_texture_stage(const aiMaterial &mat, const aiTextureType &ttype, CPT(TextureAttrib) &tattr);
+  void load_texture_stage(const aiMaterial &mat, const aiTextureType &ttype,
+                          TextureStage::Mode mode, CPT(TextureAttrib) &tattr,
+                          CPT(TexMatrixAttrib) &tmattr);
   void load_material(size_t index);
   void create_joint(Character *character, CharacterJointBundle *bundle, PartGroup *parent, const aiNode &node);
   void create_anim_channel(const aiAnimation &anim, AnimBundle *bundle, AnimGroup *parent, const aiNode &node);

+ 8 - 1
pandatool/src/assimp/config_assimp.cxx

@@ -52,7 +52,7 @@ ConfigVariableBool assimp_fix_infacing_normals
 
 ConfigVariableBool assimp_optimize_meshes
 ("assimp-optimize-meshes", true,
- PRC_DESC("Removes the number of draw calls by unifying geometry with the same "
+ PRC_DESC("Reduces the number of draw calls by unifying geometry with the same "
           "materials.  Especially effective in conjunction with "
           "assimp-optimize-graph and assimp-remove-redundant-materials."));
 
@@ -86,6 +86,13 @@ ConfigVariableDouble assimp_smooth_normal_angle
           "normals. Note that you may need to clear the model-cache after "
           "changing this."));
 
+ConfigVariableBool assimp_collapse_dummy_root_node
+("assimp-collapse-dummy-root-node", false,
+ PRC_DESC("If set to true, collapses the root node that Assimp creates, if it "
+          "appears to be a synthetic dummy root node and contains no meshes.  "
+          "This variable is new as of Panda3D 1.10.13 and will become true by "
+          "default as of Panda3D 1.11.0."));
+
 /**
  * Initializes the library.  This must be called at least once before any of
  * the functions or classes in this library can be used.  Normally it will be

+ 1 - 0
pandatool/src/assimp/config_assimp.h

@@ -33,6 +33,7 @@ extern ConfigVariableBool assimp_optimize_graph;
 extern ConfigVariableBool assimp_flip_winding_order;
 extern ConfigVariableBool assimp_gen_normals;
 extern ConfigVariableDouble assimp_smooth_normal_angle;
+extern ConfigVariableBool assimp_collapse_dummy_root_node;
 
 extern EXPCL_ASSIMP void init_libassimp();
 

+ 10 - 3
pandatool/src/assimp/pandaIOStream.cxx

@@ -27,10 +27,11 @@ PandaIOStream(std::istream &stream) : _istream(stream) {
  */
 size_t PandaIOStream::
 FileSize() const {
+  _istream.clear();
   std::streampos cur = _istream.tellg();
   _istream.seekg(0, ios::end);
   std::streampos end = _istream.tellg();
-  _istream.seekg(cur, ios::beg);
+  _istream.seekg(cur);
   return end;
 }
 
@@ -47,8 +48,14 @@ Flush() {
  */
 size_t PandaIOStream::
 Read(void *buffer, size_t size, size_t count) {
-  _istream.read((char*) buffer, size * count);
-  return _istream.gcount();
+  _istream.read((char *)buffer, size * count);
+
+  if (_istream.eof()) {
+    // Gracefully handle EOF.
+    _istream.clear(ios::eofbit);
+  }
+
+  return _istream.gcount() / size;
 }
 
 /**

+ 2 - 1
pandatool/src/ptloader/config_ptloader.cxx

@@ -80,10 +80,11 @@ init_libptloader() {
   LoaderFileTypeRegistry *reg = LoaderFileTypeRegistry::get_global_ptr();
 
   init_liblwo();
+  init_libflt();
+
   FltToEggConverter *flt = new FltToEggConverter;
   reg->register_type(new LoaderFileTypePandatool(flt));
 
-  init_libflt();
   LwoToEggConverter *lwo = new LwoToEggConverter;
   reg->register_type(new LoaderFileTypePandatool(lwo));