From 52228a93f8215ec6bb4d89f313d66049546ed884 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Thu, 6 May 2021 21:07:38 +0200 Subject: [PATCH 01/28] Fix X3DGeohelper. --- code/AssetLib/X3D/X3DGeoHelper.cpp | 530 ++++++++++++++++++++++++++ code/AssetLib/X3D/X3DGeoHelper.h | 38 ++ code/AssetLib/X3D/X3DImporter.cpp | 179 ++++++++- code/AssetLib/X3D/X3DImporter.hpp | 101 ++++- code/AssetLib/glTF/glTFExporter.cpp | 9 +- code/AssetLib/glTF2/glTF2Exporter.cpp | 50 +-- code/CMakeLists.txt | 2 + code/Common/scene.cpp | 65 ++-- code/PostProcessing/ProcessHelper.h | 4 +- include/assimp/scene.h | 2 - 10 files changed, 895 insertions(+), 85 deletions(-) create mode 100644 code/AssetLib/X3D/X3DGeoHelper.cpp create mode 100644 code/AssetLib/X3D/X3DGeoHelper.h diff --git a/code/AssetLib/X3D/X3DGeoHelper.cpp b/code/AssetLib/X3D/X3DGeoHelper.cpp new file mode 100644 index 000000000..8a078d0f5 --- /dev/null +++ b/code/AssetLib/X3D/X3DGeoHelper.cpp @@ -0,0 +1,530 @@ +#include "X3DGeoHelper.h" +#include "X3DImporter.hpp" + +#include +#include + +#include + +namespace Assimp { + +aiVector3D X3DGeoHelper::make_point2D(float angle, float radius) { + return aiVector3D(radius * std::cos(angle), radius * std::sin(angle), 0); +} + +void X3DGeoHelper::make_arc2D(float pStartAngle, float pEndAngle, float pRadius, size_t numSegments, std::list &pVertices) { + // check argument values ranges. + if ((pStartAngle < -AI_MATH_TWO_PI_F) || (pStartAngle > AI_MATH_TWO_PI_F)) { + throw DeadlyImportError("GeometryHelper_Make_Arc2D.pStartAngle"); + } + if ((pEndAngle < -AI_MATH_TWO_PI_F) || (pEndAngle > AI_MATH_TWO_PI_F)) { + throw DeadlyImportError("GeometryHelper_Make_Arc2D.pEndAngle"); + } + if (pRadius <= 0) { + throw DeadlyImportError("GeometryHelper_Make_Arc2D.pRadius"); + } + + // calculate arc angle and check type of arc + float angle_full = std::fabs(pEndAngle - pStartAngle); + if ((angle_full > AI_MATH_TWO_PI_F) || (angle_full == 0.0f)) { + angle_full = AI_MATH_TWO_PI_F; + } + + // calculate angle for one step - angle to next point of line. + float angle_step = angle_full / (float)numSegments; + // make points + for (size_t pi = 0; pi <= numSegments; pi++) { + float tangle = pStartAngle + pi * angle_step; + pVertices.emplace_back(make_point2D(tangle, pRadius)); + } // for(size_t pi = 0; pi <= pNumSegments; pi++) + + // if we making full circle then add last vertex equal to first vertex + if (angle_full == AI_MATH_TWO_PI_F) pVertices.push_back(*pVertices.begin()); +} + +void X3DGeoHelper::extend_point_to_line(const std::list &pPoint, std::list &pLine) { + std::list::const_iterator pit = pPoint.begin(); + std::list::const_iterator pit_last = pPoint.end(); + + --pit_last; + + if (pPoint.size() < 2) { + throw DeadlyImportError("GeometryHelper_Extend_PointToLine.pPoint.size() can not be less than 2."); + } + + // add first point of first line. + pLine.push_back(*pit++); + // add internal points + while (pit != pit_last) { + pLine.push_back(*pit); // second point of previous line + pLine.push_back(*pit); // first point of next line + ++pit; + } + // add last point of last line + pLine.push_back(*pit); +} + +void X3DGeoHelper::polylineIdx_to_lineIdx(const std::list &pPolylineCoordIdx, std::list &pLineCoordIdx) { + std::list::const_iterator plit = pPolylineCoordIdx.begin(); + + while (plit != pPolylineCoordIdx.end()) { + // add first point of polyline + pLineCoordIdx.push_back(*plit++); + while ((*plit != (-1)) && (plit != pPolylineCoordIdx.end())) { + std::list::const_iterator plit_next; + + plit_next = plit, ++plit_next; + pLineCoordIdx.push_back(*plit); // second point of previous line. + pLineCoordIdx.push_back(-1); // delimiter + if ((*plit_next == (-1)) || (plit_next == pPolylineCoordIdx.end())) break; // current polyline is finished + + pLineCoordIdx.push_back(*plit); // first point of next line. + plit = plit_next; + } // while((*plit != (-1)) && (plit != pPolylineCoordIdx.end())) + } // while(plit != pPolylineCoordIdx.end()) +} + +#define MACRO_FACE_ADD_QUAD_FA(pCCW, pOut, pIn, pP1, pP2, pP3, pP4) \ + do { \ + if (pCCW) { \ + pOut.push_back(pIn[pP1]); \ + pOut.push_back(pIn[pP2]); \ + pOut.push_back(pIn[pP3]); \ + pOut.push_back(pIn[pP4]); \ + } else { \ + pOut.push_back(pIn[pP4]); \ + pOut.push_back(pIn[pP3]); \ + pOut.push_back(pIn[pP2]); \ + pOut.push_back(pIn[pP1]); \ + } \ + } while (false) + +#define MESH_RectParallelepiped_CREATE_VERT \ + aiVector3D vert_set[8]; \ + float x1, x2, y1, y2, z1, z2, hs; \ + \ + hs = pSize.x / 2, x1 = -hs, x2 = hs; \ + hs = pSize.y / 2, y1 = -hs, y2 = hs; \ + hs = pSize.z / 2, z1 = -hs, z2 = hs; \ + vert_set[0].Set(x2, y1, z2); \ + vert_set[1].Set(x2, y2, z2); \ + vert_set[2].Set(x2, y2, z1); \ + vert_set[3].Set(x2, y1, z1); \ + vert_set[4].Set(x1, y1, z2); \ + vert_set[5].Set(x1, y2, z2); \ + vert_set[6].Set(x1, y2, z1); \ + vert_set[7].Set(x1, y1, z1) + +void X3DGeoHelper::rect_parallele_piped(const aiVector3D &pSize, std::list &pVertices) { + MESH_RectParallelepiped_CREATE_VERT; + MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 3, 2, 1, 0); // front + MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 6, 7, 4, 5); // back + MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 7, 3, 0, 4); // left + MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 2, 6, 5, 1); // right + MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 0, 1, 5, 4); // top + MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 7, 6, 2, 3); // bottom +} + +#undef MESH_RectParallelepiped_CREATE_VERT + +void X3DGeoHelper::coordIdx_str2faces_arr(const std::vector &pCoordIdx, std::vector &pFaces, unsigned int &pPrimitiveTypes) { + std::vector f_data(pCoordIdx); + std::vector inds; + unsigned int prim_type = 0; + + if (f_data.back() != (-1)) { + f_data.push_back(-1); + } + + // reserve average size. + pFaces.reserve(f_data.size() / 3); + inds.reserve(4); + //PrintVectorSet("build. ci", pCoordIdx); + for (std::vector::iterator it = f_data.begin(); it != f_data.end(); ++it) { + // when face is got count how many indices in it. + if (*it == (-1)) { + aiFace tface; + size_t ts; + + ts = inds.size(); + switch (ts) { + case 0: + goto mg_m_err; + case 1: + prim_type |= aiPrimitiveType_POINT; + break; + case 2: + prim_type |= aiPrimitiveType_LINE; + break; + case 3: + prim_type |= aiPrimitiveType_TRIANGLE; + break; + default: + prim_type |= aiPrimitiveType_POLYGON; + break; + } + + tface.mNumIndices = static_cast(ts); + tface.mIndices = new unsigned int[ts]; + memcpy(tface.mIndices, inds.data(), ts * sizeof(unsigned int)); + pFaces.push_back(tface); + inds.clear(); + } // if(*it == (-1)) + else { + inds.push_back(*it); + } // if(*it == (-1)) else + } // for(std::list::iterator it = f_data.begin(); it != f_data.end(); it++) + //PrintVectorSet("build. faces", pCoordIdx); + + pPrimitiveTypes = prim_type; + + return; + +mg_m_err: + for (size_t i = 0, i_e = pFaces.size(); i < i_e; i++) + delete[] pFaces.at(i).mIndices; + + pFaces.clear(); +} + +void X3DGeoHelper::add_color(aiMesh &pMesh, const std::list &pColors, const bool pColorPerVertex) { + std::list tcol; + + // create RGBA array from RGB. + for (std::list::const_iterator it = pColors.begin(); it != pColors.end(); ++it) + tcol.push_back(aiColor4D((*it).r, (*it).g, (*it).b, 1)); + + // call existing function for adding RGBA colors + add_color(pMesh, tcol, pColorPerVertex); +} + +void X3DGeoHelper::add_color(aiMesh &pMesh, const std::list &pColors, const bool pColorPerVertex) { + std::list::const_iterator col_it = pColors.begin(); + + if (pColorPerVertex) { + if (pColors.size() < pMesh.mNumVertices) { + throw DeadlyImportError("MeshGeometry_AddColor1. Colors count(" + to_string(pColors.size()) + ") can not be less than Vertices count(" + + to_string(pMesh.mNumVertices) + ")."); + } + + // copy colors to mesh + pMesh.mColors[0] = new aiColor4D[pMesh.mNumVertices]; + for (size_t i = 0; i < pMesh.mNumVertices; i++) + pMesh.mColors[0][i] = *col_it++; + } // if(pColorPerVertex) + else { + if (pColors.size() < pMesh.mNumFaces) { + throw DeadlyImportError("MeshGeometry_AddColor1. Colors count(" + to_string(pColors.size()) + ") can not be less than Faces count(" + + to_string(pMesh.mNumFaces) + ")."); + } + + // copy colors to mesh + pMesh.mColors[0] = new aiColor4D[pMesh.mNumVertices]; + for (size_t fi = 0; fi < pMesh.mNumFaces; fi++) { + // apply color to all vertices of face + for (size_t vi = 0, vi_e = pMesh.mFaces[fi].mNumIndices; vi < vi_e; vi++) { + pMesh.mColors[0][pMesh.mFaces[fi].mIndices[vi]] = *col_it; + } + + ++col_it; + } + } // if(pColorPerVertex) else +} + +void X3DGeoHelper::add_color(aiMesh &pMesh, const std::vector &pCoordIdx, const std::vector &pColorIdx, + const std::list &pColors, const bool pColorPerVertex) { + std::list tcol; + + // create RGBA array from RGB. + for (std::list::const_iterator it = pColors.begin(); it != pColors.end(); ++it) { + tcol.push_back(aiColor4D((*it).r, (*it).g, (*it).b, 1)); + } + + // call existing function for adding RGBA colors + add_color(pMesh, pCoordIdx, pColorIdx, tcol, pColorPerVertex); +} + +void X3DGeoHelper::add_color(aiMesh &pMesh, const std::vector &coordIdx, const std::vector &colorIdx, + const std::list &colors, bool pColorPerVertex) { + std::vector col_tgt_arr; + std::list col_tgt_list; + std::vector col_arr_copy; + + if (coordIdx.size() == 0) { + throw DeadlyImportError("MeshGeometry_AddColor2. pCoordIdx can not be empty."); + } + + // copy list to array because we are need indexed access to colors. + col_arr_copy.reserve(colors.size()); + for (std::list::const_iterator it = colors.begin(); it != colors.end(); ++it) { + col_arr_copy.push_back(*it); + } + + if (pColorPerVertex) { + if (colorIdx.size() > 0) { + // check indices array count. + if (colorIdx.size() < coordIdx.size()) { + throw DeadlyImportError("MeshGeometry_AddColor2. Colors indices count(" + to_string(colorIdx.size()) + + ") can not be less than Coords inidces count(" + to_string(coordIdx.size()) + ")."); + } + // create list with colors for every vertex. + col_tgt_arr.resize(pMesh.mNumVertices); + for (std::vector::const_iterator colidx_it = colorIdx.begin(), coordidx_it = coordIdx.begin(); colidx_it != colorIdx.end(); ++colidx_it, ++coordidx_it) { + if (*colidx_it == (-1)) { + continue; // skip faces delimiter + } + if ((unsigned int)(*coordidx_it) > pMesh.mNumVertices) { + throw DeadlyImportError("MeshGeometry_AddColor2. Coordinate idx is out of range."); + } + if ((unsigned int)*colidx_it > pMesh.mNumVertices) { + throw DeadlyImportError("MeshGeometry_AddColor2. Color idx is out of range."); + } + + col_tgt_arr[*coordidx_it] = col_arr_copy[*colidx_it]; + } + } // if(pColorIdx.size() > 0) + else { + // when color indices list is absent use CoordIdx. + // check indices array count. + if (colors.size() < pMesh.mNumVertices) { + throw DeadlyImportError("MeshGeometry_AddColor2. Colors count(" + to_string(colors.size()) + ") can not be less than Vertices count(" + + to_string(pMesh.mNumVertices) + ")."); + } + // create list with colors for every vertex. + col_tgt_arr.resize(pMesh.mNumVertices); + for (size_t i = 0; i < pMesh.mNumVertices; i++) { + col_tgt_arr[i] = col_arr_copy[i]; + } + } // if(pColorIdx.size() > 0) else + } // if(pColorPerVertex) + else { + if (colorIdx.size() > 0) { + // check indices array count. + if (colorIdx.size() < pMesh.mNumFaces) { + throw DeadlyImportError("MeshGeometry_AddColor2. Colors indices count(" + to_string(colorIdx.size()) + + ") can not be less than Faces count(" + to_string(pMesh.mNumFaces) + ")."); + } + // create list with colors for every vertex using faces indices. + col_tgt_arr.resize(pMesh.mNumFaces); + + std::vector::const_iterator colidx_it = colorIdx.begin(); + for (size_t fi = 0; fi < pMesh.mNumFaces; fi++) { + if ((unsigned int)*colidx_it > pMesh.mNumFaces) throw DeadlyImportError("MeshGeometry_AddColor2. Face idx is out of range."); + + col_tgt_arr[fi] = col_arr_copy[*colidx_it++]; + } + } // if(pColorIdx.size() > 0) + else { + // when color indices list is absent use CoordIdx. + // check indices array count. + if (colors.size() < pMesh.mNumFaces) { + throw DeadlyImportError("MeshGeometry_AddColor2. Colors count(" + to_string(colors.size()) + ") can not be less than Faces count(" + + to_string(pMesh.mNumFaces) + ")."); + } + // create list with colors for every vertex using faces indices. + col_tgt_arr.resize(pMesh.mNumFaces); + for (size_t fi = 0; fi < pMesh.mNumFaces; fi++) + col_tgt_arr[fi] = col_arr_copy[fi]; + + } // if(pColorIdx.size() > 0) else + } // if(pColorPerVertex) else + + // copy array to list for calling function that add colors. + for (std::vector::const_iterator it = col_tgt_arr.begin(); it != col_tgt_arr.end(); ++it) + col_tgt_list.push_back(*it); + // add prepared colors list to mesh. + add_color(pMesh, col_tgt_list, pColorPerVertex); +} + +void X3DGeoHelper::add_normal(aiMesh &pMesh, const std::vector &pCoordIdx, const std::vector &pNormalIdx, + const std::list &pNormals, const bool pNormalPerVertex) { + std::vector tind; + std::vector norm_arr_copy; + + // copy list to array because we are need indexed access to normals. + norm_arr_copy.reserve(pNormals.size()); + for (std::list::const_iterator it = pNormals.begin(); it != pNormals.end(); ++it) { + norm_arr_copy.push_back(*it); + } + + if (pNormalPerVertex) { + if (pNormalIdx.size() > 0) { + // check indices array count. + if (pNormalIdx.size() != pCoordIdx.size()) throw DeadlyImportError("Normals and Coords inidces count must be equal."); + + tind.reserve(pNormalIdx.size()); + for (std::vector::const_iterator it = pNormalIdx.begin(); it != pNormalIdx.end(); ++it) { + if (*it != (-1)) tind.push_back(*it); + } + + // copy normals to mesh + pMesh.mNormals = new aiVector3D[pMesh.mNumVertices]; + for (size_t i = 0; (i < pMesh.mNumVertices) && (i < tind.size()); i++) { + if (tind[i] >= norm_arr_copy.size()) + throw DeadlyImportError("MeshGeometry_AddNormal. Normal index(" + to_string(tind[i]) + + ") is out of range. Normals count: " + to_string(norm_arr_copy.size()) + "."); + + pMesh.mNormals[i] = norm_arr_copy[tind[i]]; + } + } else { + if (pNormals.size() != pMesh.mNumVertices) throw DeadlyImportError("MeshGeometry_AddNormal. Normals and vertices count must be equal."); + + // copy normals to mesh + pMesh.mNormals = new aiVector3D[pMesh.mNumVertices]; + std::list::const_iterator norm_it = pNormals.begin(); + for (size_t i = 0; i < pMesh.mNumVertices; i++) + pMesh.mNormals[i] = *norm_it++; + } + } // if(pNormalPerVertex) + else { + if (pNormalIdx.size() > 0) { + if (pMesh.mNumFaces != pNormalIdx.size()) throw DeadlyImportError("Normals faces count must be equal to mesh faces count."); + + std::vector::const_iterator normidx_it = pNormalIdx.begin(); + + tind.reserve(pNormalIdx.size()); + for (size_t i = 0, i_e = pNormalIdx.size(); i < i_e; i++) + tind.push_back(*normidx_it++); + + } else { + tind.reserve(pMesh.mNumFaces); + for (size_t i = 0; i < pMesh.mNumFaces; i++) + tind.push_back(i); + } + + // copy normals to mesh + pMesh.mNormals = new aiVector3D[pMesh.mNumVertices]; + for (size_t fi = 0; fi < pMesh.mNumFaces; fi++) { + aiVector3D tnorm; + + tnorm = norm_arr_copy[tind[fi]]; + for (size_t vi = 0, vi_e = pMesh.mFaces[fi].mNumIndices; vi < vi_e; vi++) + pMesh.mNormals[pMesh.mFaces[fi].mIndices[vi]] = tnorm; + } + } // if(pNormalPerVertex) else +} + +void X3DGeoHelper::add_normal(aiMesh &pMesh, const std::list &pNormals, const bool pNormalPerVertex) { + std::list::const_iterator norm_it = pNormals.begin(); + + if (pNormalPerVertex) { + if (pNormals.size() != pMesh.mNumVertices) throw DeadlyImportError("MeshGeometry_AddNormal. Normals and vertices count must be equal."); + + // copy normals to mesh + pMesh.mNormals = new aiVector3D[pMesh.mNumVertices]; + for (size_t i = 0; i < pMesh.mNumVertices; i++) + pMesh.mNormals[i] = *norm_it++; + } // if(pNormalPerVertex) + else { + if (pNormals.size() != pMesh.mNumFaces) throw DeadlyImportError("MeshGeometry_AddNormal. Normals and faces count must be equal."); + + // copy normals to mesh + pMesh.mNormals = new aiVector3D[pMesh.mNumVertices]; + for (size_t fi = 0; fi < pMesh.mNumFaces; fi++) { + // apply color to all vertices of face + for (size_t vi = 0, vi_e = pMesh.mFaces[fi].mNumIndices; vi < vi_e; vi++) + pMesh.mNormals[pMesh.mFaces[fi].mIndices[vi]] = *norm_it; + + ++norm_it; + } + } // if(pNormalPerVertex) else +} + +void X3DGeoHelper::add_tex_coord(aiMesh &pMesh, const std::vector &pCoordIdx, const std::vector &pTexCoordIdx, + const std::list &pTexCoords) { + std::vector texcoord_arr_copy; + std::vector faces; + unsigned int prim_type; + + // copy list to array because we are need indexed access to normals. + texcoord_arr_copy.reserve(pTexCoords.size()); + for (std::list::const_iterator it = pTexCoords.begin(); it != pTexCoords.end(); ++it) { + texcoord_arr_copy.push_back(aiVector3D((*it).x, (*it).y, 0)); + } + + if (pTexCoordIdx.size() > 0) { + coordIdx_str2faces_arr(pTexCoordIdx, faces, prim_type); + if (faces.empty()) { + throw DeadlyImportError("Failed to add texture coordinates to mesh, faces list is empty."); + } + if (faces.size() != pMesh.mNumFaces) { + throw DeadlyImportError("Texture coordinates faces count must be equal to mesh faces count."); + } + } else { + coordIdx_str2faces_arr(pCoordIdx, faces, prim_type); + } + + pMesh.mTextureCoords[0] = new aiVector3D[pMesh.mNumVertices]; + pMesh.mNumUVComponents[0] = 2; + for (size_t fi = 0, fi_e = faces.size(); fi < fi_e; fi++) { + if (pMesh.mFaces[fi].mNumIndices != faces.at(fi).mNumIndices) + throw DeadlyImportError("Number of indices in texture face and mesh face must be equal. Invalid face index: " + to_string(fi) + "."); + + for (size_t ii = 0; ii < pMesh.mFaces[fi].mNumIndices; ii++) { + size_t vert_idx = pMesh.mFaces[fi].mIndices[ii]; + size_t tc_idx = faces.at(fi).mIndices[ii]; + + pMesh.mTextureCoords[0][vert_idx] = texcoord_arr_copy.at(tc_idx); + } + } // for(size_t fi = 0, fi_e = faces.size(); fi < fi_e; fi++) +} + +void X3DGeoHelper::add_tex_coord(aiMesh &pMesh, const std::list &pTexCoords) { + std::vector tc_arr_copy; + + if (pTexCoords.size() != pMesh.mNumVertices) { + throw DeadlyImportError("MeshGeometry_AddTexCoord. Texture coordinates and vertices count must be equal."); + } + + // copy list to array because we are need convert aiVector2D to aiVector3D and also get indexed access as a bonus. + tc_arr_copy.reserve(pTexCoords.size()); + for (std::list::const_iterator it = pTexCoords.begin(); it != pTexCoords.end(); ++it) { + tc_arr_copy.push_back(aiVector3D((*it).x, (*it).y, 0)); + } + + // copy texture coordinates to mesh + pMesh.mTextureCoords[0] = new aiVector3D[pMesh.mNumVertices]; + pMesh.mNumUVComponents[0] = 2; + for (size_t i = 0; i < pMesh.mNumVertices; i++) { + pMesh.mTextureCoords[0][i] = tc_arr_copy[i]; + } +} + +aiMesh *X3DGeoHelper::make_mesh(const std::vector &pCoordIdx, const std::list &pVertices) { + std::vector faces; + unsigned int prim_type = 0; + + // create faces array from input string with vertices indices. + X3DGeoHelper::coordIdx_str2faces_arr(pCoordIdx, faces, prim_type); + if (!faces.size()) { + throw DeadlyImportError("Failed to create mesh, faces list is empty."); + } + + // + // Create new mesh and copy geometry data. + // + aiMesh *tmesh = new aiMesh; + size_t ts = faces.size(); + // faces + tmesh->mFaces = new aiFace[ts]; + tmesh->mNumFaces = static_cast(ts); + for (size_t i = 0; i < ts; i++) + tmesh->mFaces[i] = faces.at(i); + + // vertices + std::list::const_iterator vit = pVertices.begin(); + + ts = pVertices.size(); + tmesh->mVertices = new aiVector3D[ts]; + tmesh->mNumVertices = static_cast(ts); + for (size_t i = 0; i < ts; i++) { + tmesh->mVertices[i] = *vit++; + } + + // set primitives type and return result. + tmesh->mPrimitiveTypes = prim_type; + + return tmesh; +} + +} // namespace Assimp diff --git a/code/AssetLib/X3D/X3DGeoHelper.h b/code/AssetLib/X3D/X3DGeoHelper.h new file mode 100644 index 000000000..38b6de4dc --- /dev/null +++ b/code/AssetLib/X3D/X3DGeoHelper.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + +#include +#include + +struct aiFace; +struct aiMesh; + +namespace Assimp { + +class X3DGeoHelper { +public: + static aiVector3D make_point2D(float angle, float radius); + static void make_arc2D(float pStartAngle, float pEndAngle, float pRadius, size_t numSegments, std::list &pVertices); + static void extend_point_to_line(const std::list &pPoint, std::list &pLine); + static void polylineIdx_to_lineIdx(const std::list &pPolylineCoordIdx, std::list &pLineCoordIdx); + static void rect_parallele_piped(const aiVector3D &pSize, std::list &pVertices); + static void coordIdx_str2faces_arr(const std::vector &pCoordIdx, std::vector &pFaces, unsigned int &pPrimitiveTypes); + static void add_color(aiMesh &pMesh, const std::list &pColors, const bool pColorPerVertex); + static void add_color(aiMesh &pMesh, const std::list &pColors, const bool pColorPerVertex); + static void add_color(aiMesh &pMesh, const std::vector &pCoordIdx, const std::vector &pColorIdx, + const std::list &pColors, const bool pColorPerVertex); + static void add_color(aiMesh &pMesh, const std::vector &pCoordIdx, const std::vector &pColorIdx, + const std::list &pColors, const bool pColorPerVertex); + static void add_normal(aiMesh &pMesh, const std::vector &pCoordIdx, const std::vector &pNormalIdx, + const std::list &pNormals, const bool pNormalPerVertex); + static void add_normal(aiMesh &pMesh, const std::list &pNormals, const bool pNormalPerVertex); + static void add_tex_coord(aiMesh &pMesh, const std::vector &pCoordIdx, const std::vector &pTexCoordIdx, + const std::list &pTexCoords); + static void add_tex_coord(aiMesh &pMesh, const std::list &pTexCoords); + static aiMesh *make_mesh(const std::vector &pCoordIdx, const std::list &pVertices); +}; + +} // namespace Assimp diff --git a/code/AssetLib/X3D/X3DImporter.cpp b/code/AssetLib/X3D/X3DImporter.cpp index ff6e4f40e..341cc9d99 100644 --- a/code/AssetLib/X3D/X3DImporter.cpp +++ b/code/AssetLib/X3D/X3DImporter.cpp @@ -46,16 +46,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_X3D_IMPORTER #include "X3DImporter.hpp" -#include -// Header files, Assimp. +#include +#include #include #include // Header files, stdlib. #include #include -#include namespace Assimp { @@ -126,7 +125,8 @@ struct WordIterator { const char *WordIterator::whitespace = ", \t\r\n"; X3DImporter::X3DImporter() : - mNodeElementCur(nullptr) { + mNodeElementCur(nullptr), + mScene(nullptr) { // empty } @@ -153,10 +153,29 @@ void X3DImporter::ParseFile(const std::string &file, IOSystem *pIOHandler) { std::unique_ptr fileStream(pIOHandler->Open(file, mode)); if (!fileStream.get()) { throw DeadlyImportError("Failed to open file " + file + "."); - } + } + + XmlParser theParser; + if (!theParser.parse(fileStream.get())) { + return; + } + + XmlNode *node = theParser.findNode("X3D"); + if (nullptr == node) { + return; + } + + for (auto ¤tNode : node->children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "head") { + readMetadata(currentNode); + } else if (currentName == "Scene") { + readScene(currentNode); + } + } } -bool X3DImporter::CanRead( const std::string &pFile, IOSystem * /*pIOHandler*/, bool checkSig ) const { +bool X3DImporter::CanRead(const std::string &pFile, IOSystem * /*pIOHandler*/, bool checkSig) const { if (checkSig) { std::string::size_type pos = pFile.find_last_of(".x3d"); if (pos != std::string::npos) { @@ -167,16 +186,17 @@ bool X3DImporter::CanRead( const std::string &pFile, IOSystem * /*pIOHandler*/, return false; } -void X3DImporter::GetExtensionList( std::set &extensionList ) { +void X3DImporter::GetExtensionList(std::set &extensionList) { extensionList.insert("x3d"); } -void X3DImporter::InternReadFile( const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler ) { +void X3DImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { std::shared_ptr stream(pIOHandler->Open(pFile, "rb")); if (!stream) { throw DeadlyImportError("Could not open file for reading"); } + mScene = pScene; pScene->mRootNode = new aiNode(pFile); } @@ -184,6 +204,147 @@ const aiImporterDesc *X3DImporter::GetInfo() const { return &Description; } -} +struct meta_entry { + std::string name; + std::string value; +}; + +void X3DImporter::readMetadata(XmlNode &node) { + std::vector metaArray; + for (auto currentNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "meta") { + meta_entry entry; + if (XmlParser::getStdStrAttribute(currentNode, "name", entry.name)) { + XmlParser::getStdStrAttribute(currentNode, "content", entry.value); + metaArray.emplace_back(entry); + } + } + } + mScene->mMetaData = aiMetadata::Alloc(static_cast(metaArray.size())); + unsigned int i = 0; + for (auto currentMeta : metaArray) { + mScene->mMetaData->Set(i, currentMeta.name, currentMeta.value); + ++i; + } +} + +void X3DImporter::readScene(XmlNode &node) { + for (auto currentNode : node.children()) { + const std::string ¤tName = currentNode.name(); + if (currentName == "Viewpoint") { + readViewpoint(currentNode); + } + } +} + +void X3DImporter::readViewpoint(XmlNode &node) { + for (auto currentNode : node.children()) { + //const std::string ¤tName = currentNode.name(); + } +} + +void readMetadataBoolean(XmlNode &node, X3DNodeElementBase *parent) { + std::string val; + X3DNodeElementMetaBoolean *boolean = nullptr; + if (XmlParser::getStdStrAttribute(node, "value", val)) { + std::vector values; + tokenize(val, values, " "); + boolean = new X3DNodeElementMetaBoolean(parent); + for (size_t i = 0; i < values.size(); ++i) { + bool current_boolean = false; + if (values[i] == "true") { + current_boolean = true; + } + boolean->Value.emplace_back(current_boolean); + } + } +} + +void readMetadataDouble(XmlNode &node, X3DNodeElementBase *parent) { + std::string val; + X3DNodeElementMetaDouble *doubleNode = nullptr; + if (XmlParser::getStdStrAttribute(node, "value", val)) { + std::vector values; + tokenize(val, values, " "); + doubleNode = new X3DNodeElementMetaDouble(parent); + for (size_t i = 0; i < values.size(); ++i) { + double current_double = static_cast(fast_atof(values[i].c_str())); + doubleNode->Value.emplace_back(current_double); + } + } +} + +void readMetadataFloat(XmlNode &node, X3DNodeElementBase *parent) { + std::string val; + X3DNodeElementMetaFloat *floatNode = nullptr; + if (XmlParser::getStdStrAttribute(node, "value", val)) { + std::vector values; + tokenize(val, values, " "); + floatNode = new X3DNodeElementMetaFloat(parent); + for (size_t i = 0; i < values.size(); ++i) { + float current_float = static_cast(fast_atof(values[i].c_str())); + floatNode->Value.emplace_back(current_float); + } + } +} + +void readMetadataInteger(XmlNode &node, X3DNodeElementBase *parent) { + std::string val; + X3DNodeElementMetaInt *intNode = nullptr; + if (XmlParser::getStdStrAttribute(node, "value", val)) { + std::vector values; + tokenize(val, values, " "); + intNode = new X3DNodeElementMetaInt(parent); + for (size_t i = 0; i < values.size(); ++i) { + int current_int = static_cast(std::atoi(values[i].c_str())); + intNode->Value.emplace_back(current_int); + } + } +} + +void readMetadataSet(XmlNode &node, X3DNodeElementBase *parent) { + std::string val; + X3DNodeElementMetaSet *setNode = new X3DNodeElementMetaSet(parent); + if (XmlParser::getStdStrAttribute(node, "name", val)) { + setNode->Name = val; + } + + if (XmlParser::getStdStrAttribute(node, "reference", val)) { + setNode->Reference = val; + } +} + +void readMetadataString(XmlNode &node, X3DNodeElementBase *parent) { + std::string val; + X3DNodeElementMetaString *strNode = nullptr; + if (XmlParser::getStdStrAttribute(node, "value", val)) { + std::vector values; + tokenize(val, values, " "); + strNode = new X3DNodeElementMetaString(parent); + for (size_t i = 0; i < values.size(); ++i) { + strNode->Value.emplace_back(values[i]); + } + } +} + +void X3DImporter::readMetadataObject(XmlNode &node) { + const std::string &name = node.name(); + if (name == "MetadataBoolean") { + readMetadataBoolean(node, mNodeElementCur); + } else if (name == "MetadataDouble") { + readMetadataDouble(node, mNodeElementCur); + } else if (name == "MetadataFloat") { + readMetadataFloat(node, mNodeElementCur); + } else if (name == "MetadataInteger") { + readMetadataInteger(node, mNodeElementCur); + } else if (name == "MetadataSet") { + readMetadataSet(node, mNodeElementCur); + } else if (name == "MetadataString") { + readMetadataString(node, mNodeElementCur); + } +} + +} // namespace Assimp #endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/code/AssetLib/X3D/X3DImporter.hpp b/code/AssetLib/X3D/X3DImporter.hpp index 4444bacd0..1b6410c36 100644 --- a/code/AssetLib/X3D/X3DImporter.hpp +++ b/code/AssetLib/X3D/X3DImporter.hpp @@ -38,16 +38,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/// \file X3DImporter.hpp -/// \brief X3D-format files importer for Assimp. -/// \date 2015-2016 -/// \author smal.root@gmail.com -// Thanks to acorn89 for support. - #ifndef INCLUDED_AI_X3D_IMPORTER_H #define INCLUDED_AI_X3D_IMPORTER_H -// Header files, Assimp. + #include #include #include @@ -56,7 +50,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include +#include namespace Assimp { @@ -282,8 +278,90 @@ enum class X3DElemType { struct X3DNodeElementBase { X3DNodeElementBase *Parent; std::string ID; - std::list Child; + std::list Children; X3DElemType Type; + +protected: + X3DNodeElementBase(X3DElemType type, X3DNodeElementBase *pParent) : + Type(type), Parent(pParent) { + // empty + } +}; + +struct CX3DNodeElementGroup : X3DNodeElementBase { + aiMatrix4x4 Transformation; ///< Transformation matrix. + bool Static; + bool UseChoice; ///< Flag: if true then use number from \ref Choice to choose what the child will be kept. + int32_t Choice; ///< Number of the child which will be kept. +}; + +struct X3DNodeElementMeta : X3DNodeElementBase { + std::string Name; ///< Name of metadata object. + std::string Reference; + + virtual ~X3DNodeElementMeta() { + // empty + } + +protected: + X3DNodeElementMeta(X3DElemType type, X3DNodeElementBase *parent) : + X3DNodeElementBase(type, parent) { + // empty + } +}; + +struct X3DNodeElementMetaBoolean : X3DNodeElementMeta { + std::vector Value; ///< Stored value. + + X3DNodeElementMetaBoolean(X3DNodeElementBase *pParent) : + X3DNodeElementMeta(X3DElemType::ENET_MetaBoolean, pParent) { + // empty + } +}; + +struct X3DNodeElementMetaDouble : X3DNodeElementMeta { + std::vector Value; ///< Stored value. + + X3DNodeElementMetaDouble(X3DNodeElementBase *pParent) : + X3DNodeElementMeta(X3DElemType::ENET_MetaDouble, pParent) { + // empty + } +}; + +struct X3DNodeElementMetaFloat : public X3DNodeElementMeta { + std::vector Value; ///< Stored value. + + X3DNodeElementMetaFloat(X3DNodeElementBase *pParent) : + X3DNodeElementMeta(X3DElemType::ENET_MetaFloat, pParent) { + // empty + } +}; + +struct X3DNodeElementMetaInt : public X3DNodeElementMeta { + std::vector Value; ///< Stored value. + + X3DNodeElementMetaInt(X3DNodeElementBase *pParent) : + X3DNodeElementMeta(X3DElemType::ENET_MetaInteger, pParent) { + // empty + } +}; + +struct X3DNodeElementMetaSet : public X3DNodeElementMeta { + std::list Value; ///< Stored value. + + X3DNodeElementMetaSet(X3DNodeElementBase *pParent) : + X3DNodeElementMeta(X3DElemType::ENET_MetaSet, pParent) { + // empty + } +}; + +struct X3DNodeElementMetaString : public X3DNodeElementMeta { + std::list Value; ///< Stored value. + + X3DNodeElementMetaString(X3DNodeElementBase *pParent) : + X3DNodeElementMeta(X3DElemType::ENET_MetaString, pParent) { + // empty + } }; class X3DImporter : public BaseImporter { @@ -311,10 +389,15 @@ public: void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler); const aiImporterDesc *GetInfo() const; void Clear(); + void readMetadata(XmlNode &node); + void readScene(XmlNode &node); + void readViewpoint(XmlNode &node); + void readMetadataObject(XmlNode &node); private: static const aiImporterDesc Description; - X3DNodeElementBase *mNodeElementCur; ///< Current element. + X3DNodeElementBase *mNodeElementCur; + aiScene *mScene; }; // class X3DImporter } // namespace Assimp diff --git a/code/AssetLib/glTF/glTFExporter.cpp b/code/AssetLib/glTF/glTFExporter.cpp index 1951167c6..528604cd8 100644 --- a/code/AssetLib/glTF/glTFExporter.cpp +++ b/code/AssetLib/glTF/glTFExporter.cpp @@ -322,8 +322,8 @@ void glTFExporter::GetTexSampler(const aiMaterial* mat, glTF::TexProperty& prop) prop.texture->sampler->minFilter = SamplerMinFilter_Linear; } -void glTFExporter::GetMatColorOrTex(const aiMaterial* mat, glTF::TexProperty& prop, const char* propName, int type, int idx, aiTextureType tt) -{ +void glTFExporter::GetMatColorOrTex(const aiMaterial* mat, glTF::TexProperty& prop, + const char* propName, int type, int idx, aiTextureType tt) { aiString tex; aiColor4D col; if (mat->GetTextureCount(tt) > 0) { @@ -370,7 +370,10 @@ void glTFExporter::GetMatColorOrTex(const aiMaterial* mat, glTF::TexProperty& pr } if (mat->Get(propName, type, idx, col) == AI_SUCCESS) { - prop.color[0] = col.r; prop.color[1] = col.g; prop.color[2] = col.b; prop.color[3] = col.a; + prop.color[0] = col.r; + prop.color[1] = col.g; + prop.color[2] = col.b; + prop.color[3] = col.a; } } diff --git a/code/AssetLib/glTF2/glTF2Exporter.cpp b/code/AssetLib/glTF2/glTF2Exporter.cpp index 565117ddb..8eaf3c169 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.cpp +++ b/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -1297,24 +1297,24 @@ void glTF2Exporter::ExportMetadata() } } -inline Ref GetSamplerInputRef(Asset& asset, std::string& animId, Ref& buffer, std::vector& times) -{ +inline Ref GetSamplerInputRef(Asset& asset, std::string& animId, + Ref& buffer, std::vector& times) { return ExportData(asset, animId, buffer, (unsigned int)times.size(), ×[0], AttribType::SCALAR, AttribType::SCALAR, ComponentType_FLOAT); } -inline void ExtractTranslationSampler(Asset& asset, std::string& animId, Ref& buffer, const aiNodeAnim* nodeChannel, float ticksPerSecond, Animation::Sampler& sampler) -{ +inline void ExtractTranslationSampler(Asset& asset, std::string& animId, Ref& buffer, + const aiNodeAnim* nodeChannel, float ticksPerSecond, Animation::Sampler& sampler) { const unsigned int numKeyframes = nodeChannel->mNumPositionKeys; - std::vector times(numKeyframes); - std::vector values(numKeyframes * 3); + std::vector times(numKeyframes); + std::vector values(numKeyframes * 3); for (unsigned int i = 0; i < numKeyframes; ++i) { const aiVectorKey& key = nodeChannel->mPositionKeys[i]; // mTime is measured in ticks, but GLTF time is measured in seconds, so convert. times[i] = static_cast(key.mTime / ticksPerSecond); - values[(i * 3) + 0] = key.mValue.x; - values[(i * 3) + 1] = key.mValue.y; - values[(i * 3) + 2] = key.mValue.z; + values[(i * 3) + 0] = (ai_real) key.mValue.x; + values[(i * 3) + 1] = (ai_real) key.mValue.y; + values[(i * 3) + 2] = (ai_real) key.mValue.z; } sampler.input = GetSamplerInputRef(asset, animId, buffer, times); @@ -1322,19 +1322,19 @@ inline void ExtractTranslationSampler(Asset& asset, std::string& animId, Ref& buffer, const aiNodeAnim* nodeChannel, float ticksPerSecond, Animation::Sampler& sampler) -{ +inline void ExtractScaleSampler(Asset& asset, std::string& animId, Ref& buffer, + const aiNodeAnim* nodeChannel, float ticksPerSecond, Animation::Sampler& sampler) { const unsigned int numKeyframes = nodeChannel->mNumScalingKeys; - std::vector times(numKeyframes); - std::vector values(numKeyframes * 3); + std::vector times(numKeyframes); + std::vector values(numKeyframes * 3); for (unsigned int i = 0; i < numKeyframes; ++i) { const aiVectorKey& key = nodeChannel->mScalingKeys[i]; // mTime is measured in ticks, but GLTF time is measured in seconds, so convert. times[i] = static_cast(key.mTime / ticksPerSecond); - values[(i * 3) + 0] = key.mValue.x; - values[(i * 3) + 1] = key.mValue.y; - values[(i * 3) + 2] = key.mValue.z; + values[(i * 3) + 0] = (ai_real) key.mValue.x; + values[(i * 3) + 1] = (ai_real) key.mValue.y; + values[(i * 3) + 2] = (ai_real) key.mValue.z; } sampler.input = GetSamplerInputRef(asset, animId, buffer, times); @@ -1342,20 +1342,20 @@ inline void ExtractScaleSampler(Asset& asset, std::string& animId, Ref& sampler.interpolation = Interpolation_LINEAR; } -inline void ExtractRotationSampler(Asset& asset, std::string& animId, Ref& buffer, const aiNodeAnim* nodeChannel, float ticksPerSecond, Animation::Sampler& sampler) -{ +inline void ExtractRotationSampler(Asset& asset, std::string& animId, Ref& buffer, + const aiNodeAnim* nodeChannel, float ticksPerSecond, Animation::Sampler& sampler) { const unsigned int numKeyframes = nodeChannel->mNumRotationKeys; - std::vector times(numKeyframes); - std::vector values(numKeyframes * 4); + std::vector times(numKeyframes); + std::vector values(numKeyframes * 4); for (unsigned int i = 0; i < numKeyframes; ++i) { const aiQuatKey& key = nodeChannel->mRotationKeys[i]; // mTime is measured in ticks, but GLTF time is measured in seconds, so convert. times[i] = static_cast(key.mTime / ticksPerSecond); - values[(i * 4) + 0] = key.mValue.x; - values[(i * 4) + 1] = key.mValue.y; - values[(i * 4) + 2] = key.mValue.z; - values[(i * 4) + 3] = key.mValue.w; + values[(i * 4) + 0] = (ai_real) key.mValue.x; + values[(i * 4) + 1] = (ai_real) key.mValue.y; + values[(i * 4) + 2] = (ai_real) key.mValue.z; + values[(i * 4) + 3] = (ai_real) key.mValue.w; } sampler.input = GetSamplerInputRef(asset, animId, buffer, times); @@ -1417,7 +1417,7 @@ void glTF2Exporter::ExportAnimations() } } - // Assimp documentation staes this is not used (not implemented) + // Assimp documentation states this is not used (not implemented) // for (unsigned int channelIndex = 0; channelIndex < anim->mNumMeshChannels; ++channelIndex) { // const aiMeshAnim* meshChannel = anim->mMeshChannels[channelIndex]; // } diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index bbcad86e5..0dbbd85c4 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -798,6 +798,8 @@ ADD_ASSIMP_IMPORTER( X ADD_ASSIMP_IMPORTER( X3D AssetLib/X3D/X3DImporter.cpp AssetLib/X3D/X3DImporter.hpp + AssetLib/X3D/X3DGeoHelper.cpp + AssetLib/X3D/X3DGeoHelper.h ) ADD_ASSIMP_IMPORTER( GLTF diff --git a/code/Common/scene.cpp b/code/Common/scene.cpp index f56562b1c..12667f530 100644 --- a/code/Common/scene.cpp +++ b/code/Common/scene.cpp @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2020, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -42,25 +40,25 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include -aiNode::aiNode() -: mName("") -, mParent(nullptr) -, mNumChildren(0) -, mChildren(nullptr) -, mNumMeshes(0) -, mMeshes(nullptr) -, mMetaData(nullptr) { +aiNode::aiNode() : + mName(""), + mParent(nullptr), + mNumChildren(0), + mChildren(nullptr), + mNumMeshes(0), + mMeshes(nullptr), + mMetaData(nullptr) { // empty } -aiNode::aiNode(const std::string& name) -: mName(name) -, mParent(nullptr) -, mNumChildren(0) -, mChildren(nullptr) -, mNumMeshes(0) -, mMeshes(nullptr) -, mMetaData(nullptr) { +aiNode::aiNode(const std::string &name) : + mName(name), + mParent(nullptr), + mNumChildren(0), + mChildren(nullptr), + mNumMeshes(0), + mMeshes(nullptr), + mMetaData(nullptr) { // empty } @@ -68,8 +66,7 @@ aiNode::aiNode(const std::string& name) aiNode::~aiNode() { // delete all children recursively // to make sure we won't crash if the data is invalid ... - if (mNumChildren && mChildren) - { + if (mNumChildren && mChildren) { for (unsigned int a = 0; a < mNumChildren; a++) delete mChildren[a]; } @@ -78,7 +75,7 @@ aiNode::~aiNode() { delete mMetaData; } -const aiNode *aiNode::FindNode(const char* name) const { +const aiNode *aiNode::FindNode(const char *name) const { if (nullptr == name) { return nullptr; } @@ -86,7 +83,7 @@ const aiNode *aiNode::FindNode(const char* name) const { return this; } for (unsigned int i = 0; i < mNumChildren; ++i) { - const aiNode* const p = mChildren[i]->FindNode(name); + const aiNode *const p = mChildren[i]->FindNode(name); if (p) { return p; } @@ -95,11 +92,10 @@ const aiNode *aiNode::FindNode(const char* name) const { return nullptr; } -aiNode *aiNode::FindNode(const char* name) { - if (!::strcmp(mName.data, name))return this; - for (unsigned int i = 0; i < mNumChildren; ++i) - { - aiNode* const p = mChildren[i]->FindNode(name); +aiNode *aiNode::FindNode(const char *name) { + if (!::strcmp(mName.data, name)) return this; + for (unsigned int i = 0; i < mNumChildren; ++i) { + aiNode *const p = mChildren[i]->FindNode(name); if (p) { return p; } @@ -121,17 +117,16 @@ void aiNode::addChildren(unsigned int numChildren, aiNode **children) { } if (mNumChildren > 0) { - aiNode **tmp = new aiNode*[mNumChildren]; - ::memcpy(tmp, mChildren, sizeof(aiNode*) * mNumChildren); + aiNode **tmp = new aiNode *[mNumChildren]; + ::memcpy(tmp, mChildren, sizeof(aiNode *) * mNumChildren); delete[] mChildren; - mChildren = new aiNode*[mNumChildren + numChildren]; - ::memcpy(mChildren, tmp, sizeof(aiNode*) * mNumChildren); - ::memcpy(&mChildren[mNumChildren], children, sizeof(aiNode*)* numChildren); + mChildren = new aiNode *[mNumChildren + numChildren]; + ::memcpy(mChildren, tmp, sizeof(aiNode *) * mNumChildren); + ::memcpy(&mChildren[mNumChildren], children, sizeof(aiNode *) * numChildren); mNumChildren += numChildren; delete[] tmp; - } - else { - mChildren = new aiNode*[numChildren]; + } else { + mChildren = new aiNode *[numChildren]; for (unsigned int i = 0; i < numChildren; i++) { mChildren[i] = children[i]; } diff --git a/code/PostProcessing/ProcessHelper.h b/code/PostProcessing/ProcessHelper.h index 8520b21ec..e851c97e9 100644 --- a/code/PostProcessing/ProcessHelper.h +++ b/code/PostProcessing/ProcessHelper.h @@ -133,12 +133,12 @@ inline ::aiQuatKey max(const ::aiQuatKey &a, const ::aiQuatKey &b) { // std::min for aiVertexWeight inline ::aiVertexWeight min(const ::aiVertexWeight &a, const ::aiVertexWeight &b) { - return ::aiVertexWeight(min(a.mVertexId, b.mVertexId), min(a.mWeight, b.mWeight)); + return ::aiVertexWeight(min(a.mVertexId, b.mVertexId),static_cast(min(a.mWeight, b.mWeight))); } // std::max for aiVertexWeight inline ::aiVertexWeight max(const ::aiVertexWeight &a, const ::aiVertexWeight &b) { - return ::aiVertexWeight(max(a.mVertexId, b.mVertexId), max(a.mWeight, b.mWeight)); + return ::aiVertexWeight(static_cast(max(a.mVertexId, b.mVertexId)), static_cast(max(a.mWeight, b.mWeight))); } } // end namespace std diff --git a/include/assimp/scene.h b/include/assimp/scene.h index 2a9a77b02..b8f034ebf 100644 --- a/include/assimp/scene.h +++ b/include/assimp/scene.h @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2020, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, From 56bfa1ce5c0dc472b2faaa531d0587b0c9fa3363 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Fri, 7 May 2021 11:36:21 +0200 Subject: [PATCH 02/28] Make constructros with one arg explicit --- code/AssetLib/X3D/X3DImporter.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/code/AssetLib/X3D/X3DImporter.hpp b/code/AssetLib/X3D/X3DImporter.hpp index b3a7c4d06..360dfe6c5 100644 --- a/code/AssetLib/X3D/X3DImporter.hpp +++ b/code/AssetLib/X3D/X3DImporter.hpp @@ -313,7 +313,7 @@ protected: struct X3DNodeElementMetaBoolean : X3DNodeElementMeta { std::vector Value; ///< Stored value. - X3DNodeElementMetaBoolean(X3DNodeElementBase *pParent) : + explicit X3DNodeElementMetaBoolean(X3DNodeElementBase *pParent) : X3DNodeElementMeta(X3DElemType::ENET_MetaBoolean, pParent) { // empty } @@ -322,7 +322,7 @@ struct X3DNodeElementMetaBoolean : X3DNodeElementMeta { struct X3DNodeElementMetaDouble : X3DNodeElementMeta { std::vector Value; ///< Stored value. - X3DNodeElementMetaDouble(X3DNodeElementBase *pParent) : + explicit X3DNodeElementMetaDouble(X3DNodeElementBase *pParent) : X3DNodeElementMeta(X3DElemType::ENET_MetaDouble, pParent) { // empty } @@ -331,7 +331,7 @@ struct X3DNodeElementMetaDouble : X3DNodeElementMeta { struct X3DNodeElementMetaFloat : public X3DNodeElementMeta { std::vector Value; ///< Stored value. - X3DNodeElementMetaFloat(X3DNodeElementBase *pParent) : + explicit X3DNodeElementMetaFloat(X3DNodeElementBase *pParent) : X3DNodeElementMeta(X3DElemType::ENET_MetaFloat, pParent) { // empty } @@ -340,7 +340,7 @@ struct X3DNodeElementMetaFloat : public X3DNodeElementMeta { struct X3DNodeElementMetaInt : public X3DNodeElementMeta { std::vector Value; ///< Stored value. - X3DNodeElementMetaInt(X3DNodeElementBase *pParent) : + explicit X3DNodeElementMetaInt(X3DNodeElementBase *pParent) : X3DNodeElementMeta(X3DElemType::ENET_MetaInteger, pParent) { // empty } @@ -349,7 +349,7 @@ struct X3DNodeElementMetaInt : public X3DNodeElementMeta { struct X3DNodeElementMetaSet : public X3DNodeElementMeta { std::list Value; ///< Stored value. - X3DNodeElementMetaSet(X3DNodeElementBase *pParent) : + explicit X3DNodeElementMetaSet(X3DNodeElementBase *pParent) : X3DNodeElementMeta(X3DElemType::ENET_MetaSet, pParent) { // empty } @@ -358,7 +358,7 @@ struct X3DNodeElementMetaSet : public X3DNodeElementMeta { struct X3DNodeElementMetaString : public X3DNodeElementMeta { std::list Value; ///< Stored value. - X3DNodeElementMetaString(X3DNodeElementBase *pParent) : + explicit X3DNodeElementMetaString(X3DNodeElementBase *pParent) : X3DNodeElementMeta(X3DElemType::ENET_MetaString, pParent) { // empty } From 8cae8c5461e61f02de404a415f43c17a730c5cb3 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Fri, 7 May 2021 11:52:16 +0200 Subject: [PATCH 03/28] Fix static code analysis findings --- code/AssetLib/X3D/X3DImporter.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/code/AssetLib/X3D/X3DImporter.hpp b/code/AssetLib/X3D/X3DImporter.hpp index 360dfe6c5..f65dda559 100644 --- a/code/AssetLib/X3D/X3DImporter.hpp +++ b/code/AssetLib/X3D/X3DImporter.hpp @@ -290,9 +290,6 @@ protected: struct CX3DNodeElementGroup : X3DNodeElementBase { aiMatrix4x4 Transformation; ///< Transformation matrix. - bool Static; - bool UseChoice; ///< Flag: if true then use number from \ref Choice to choose what the child will be kept. - int32_t Choice; ///< Number of the child which will be kept. }; struct X3DNodeElementMeta : X3DNodeElementBase { From 3a32612b71d4b00ca100b332268b0b272ffc14ba Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Thu, 3 Jun 2021 20:55:31 +0200 Subject: [PATCH 04/28] Add skipping of unused nodes. --- code/AssetLib/X3D/X3DImporter.cpp | 97 +++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/code/AssetLib/X3D/X3DImporter.cpp b/code/AssetLib/X3D/X3DImporter.cpp index 24814876b..0fcfec726 100644 --- a/code/AssetLib/X3D/X3DImporter.cpp +++ b/code/AssetLib/X3D/X3DImporter.cpp @@ -124,6 +124,103 @@ struct WordIterator { const char *WordIterator::whitespace = ", \t\r\n"; +void skipUnsupportedNode(const std::string &pParentNodeName, XmlNode &node) { + static const size_t Uns_Skip_Len = 192; + static const char *Uns_Skip[Uns_Skip_Len] = { + // CAD geometry component + "CADAssembly", "CADFace", "CADLayer", "CADPart", "IndexedQuadSet", "QuadSet", + // Core + "ROUTE", "ExternProtoDeclare", "ProtoDeclare", "ProtoInstance", "ProtoInterface", "WorldInfo", + // Distributed interactive simulation (DIS) component + "DISEntityManager", "DISEntityTypeMapping", "EspduTransform", "ReceiverPdu", "SignalPdu", "TransmitterPdu", + // Cube map environmental texturing component + "ComposedCubeMapTexture", "GeneratedCubeMapTexture", "ImageCubeMapTexture", + // Environmental effects component + "Background", "Fog", "FogCoordinate", "LocalFog", "TextureBackground", + // Environmental sensor component + "ProximitySensor", "TransformSensor", "VisibilitySensor", + // Followers component + "ColorChaser", "ColorDamper", "CoordinateChaser", "CoordinateDamper", "OrientationChaser", "OrientationDamper", "PositionChaser", "PositionChaser2D", + "PositionDamper", "PositionDamper2D", "ScalarChaser", "ScalarDamper", "TexCoordChaser2D", "TexCoordDamper2D", + // Geospatial component + "GeoCoordinate", "GeoElevationGrid", "GeoLocation", "GeoLOD", "GeoMetadata", "GeoOrigin", "GeoPositionInterpolator", "GeoProximitySensor", + "GeoTouchSensor", "GeoTransform", "GeoViewpoint", + // Humanoid Animation (H-Anim) component + "HAnimDisplacer", "HAnimHumanoid", "HAnimJoint", "HAnimSegment", "HAnimSite", + // Interpolation component + "ColorInterpolator", "CoordinateInterpolator", "CoordinateInterpolator2D", "EaseInEaseOut", "NormalInterpolator", "OrientationInterpolator", + "PositionInterpolator", "PositionInterpolator2D", "ScalarInterpolator", "SplinePositionInterpolator", "SplinePositionInterpolator2D", + "SplineScalarInterpolator", "SquadOrientationInterpolator", + // Key device sensor component + "KeySensor", "StringSensor", + // Layering component + "Layer", "LayerSet", "Viewport", + // Layout component + "Layout", "LayoutGroup", "LayoutLayer", "ScreenFontStyle", "ScreenGroup", + // Navigation component + "Billboard", "Collision", "LOD", "NavigationInfo", "OrthoViewpoint", "Viewpoint", "ViewpointGroup", + // Networking component + "EXPORT", "IMPORT", "Anchor", "LoadSensor", + // NURBS component + "Contour2D", "ContourPolyline2D", "CoordinateDouble", "NurbsCurve", "NurbsCurve2D", "NurbsOrientationInterpolator", "NurbsPatchSurface", + "NurbsPositionInterpolator", "NurbsSet", "NurbsSurfaceInterpolator", "NurbsSweptSurface", "NurbsSwungSurface", "NurbsTextureCoordinate", + "NurbsTrimmedSurface", + // Particle systems component + "BoundedPhysicsModel", "ConeEmitter", "ExplosionEmitter", "ForcePhysicsModel", "ParticleSystem", "PointEmitter", "PolylineEmitter", "SurfaceEmitter", + "VolumeEmitter", "WindPhysicsModel", + // Picking component + "LinePickSensor", "PickableGroup", "PointPickSensor", "PrimitivePickSensor", "VolumePickSensor", + // Pointing device sensor component + "CylinderSensor", "PlaneSensor", "SphereSensor", "TouchSensor", + // Rendering component + "ClipPlane", + // Rigid body physics + "BallJoint", "CollidableOffset", "CollidableShape", "CollisionCollection", "CollisionSensor", "CollisionSpace", "Contact", "DoubleAxisHingeJoint", + "MotorJoint", "RigidBody", "RigidBodyCollection", "SingleAxisHingeJoint", "SliderJoint", "UniversalJoint", + // Scripting component + "Script", + // Programmable shaders component + "ComposedShader", "FloatVertexAttribute", "Matrix3VertexAttribute", "Matrix4VertexAttribute", "PackagedShader", "ProgramShader", "ShaderPart", + "ShaderProgram", + // Shape component + "FillProperties", "LineProperties", "TwoSidedMaterial", + // Sound component + "AudioClip", "Sound", + // Text component + "FontStyle", "Text", + // Texturing3D Component + "ComposedTexture3D", "ImageTexture3D", "PixelTexture3D", "TextureCoordinate3D", "TextureCoordinate4D", "TextureTransformMatrix3D", "TextureTransform3D", + // Texturing component + "MovieTexture", "MultiTexture", "MultiTextureCoordinate", "MultiTextureTransform", "PixelTexture", "TextureCoordinateGenerator", "TextureProperties", + // Time component + "TimeSensor", + // Event Utilities component + "BooleanFilter", "BooleanSequencer", "BooleanToggle", "BooleanTrigger", "IntegerSequencer", "IntegerTrigger", "TimeTrigger", + // Volume rendering component + "BlendedVolumeStyle", "BoundaryEnhancementVolumeStyle", "CartoonVolumeStyle", "ComposedVolumeStyle", "EdgeEnhancementVolumeStyle", "IsoSurfaceVolumeData", + "OpacityMapVolumeStyle", "ProjectionVolumeStyle", "SegmentedVolumeData", "ShadedVolumeStyle", "SilhouetteEnhancementVolumeStyle", "ToneMappedVolumeStyle", + "VolumeData" + }; + + const std::string nn = node.name(); + bool found = false; + bool close_found = false; + + for (size_t i = 0; i < Uns_Skip_Len; i++) { + if (nn == Uns_Skip[i]) { + found = true; + if (node.empty()) { + close_found = true; + break; + } + } + } + + if (!found) throw DeadlyImportError("Unknown node \"" + nn + "\" in " + pParentNodeName + "."); + + LogInfo("Skipping node \"" + nn + "\" in " + pParentNodeName + "."); +} + X3DImporter::X3DImporter() : mNodeElementCur(nullptr), mScene(nullptr) { From ebb9b1b2af421698ed92eba7436e21b933736fba Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Thu, 10 Jun 2021 23:36:07 +0200 Subject: [PATCH 05/28] Next iteration --- code/AssetLib/X3D/X3DImporter.cpp | 2455 ++++++++++++++++++++++++++++- code/AssetLib/X3D/X3DImporter.hpp | 278 +++- 2 files changed, 2729 insertions(+), 4 deletions(-) diff --git a/code/AssetLib/X3D/X3DImporter.cpp b/code/AssetLib/X3D/X3DImporter.cpp index 0fcfec726..4c6f24795 100644 --- a/code/AssetLib/X3D/X3DImporter.cpp +++ b/code/AssetLib/X3D/X3DImporter.cpp @@ -288,9 +288,61 @@ void X3DImporter::InternReadFile( const std::string &pFile, aiScene *pScene, IOS if (!stream) { throw DeadlyImportError("Could not open file for reading"); } + std::string::size_type slashPos = pFile.find_last_of("\\/"); + pIOHandler->PushDirectory(slashPos == std::string::npos ? std::string() : pFile.substr(0, slashPos + 1)); + ParseFile(pFile, pIOHandler); + pIOHandler->PopDirectory(); + + // mScene = pScene; pScene->mRootNode = new aiNode(pFile); + pScene->mRootNode->mParent = nullptr; + pScene->mFlags |= AI_SCENE_FLAGS_ALLOW_SHARED; + + //search for root node element + + mNodeElementCur = NodeElement_List.front(); + while (mNodeElementCur->Parent != nullptr) { + mNodeElementCur = mNodeElementCur->Parent; + } + + { // fill aiScene with objects. + std::list mesh_list; + std::list mat_list; + std::list light_list; + + // create nodes tree + Postprocess_BuildNode(*mNodeElementCur, *pScene->mRootNode, mesh_list, mat_list, light_list); + // copy needed data to scene + if (!mesh_list.empty()) { + std::list::const_iterator it = mesh_list.begin(); + + pScene->mNumMeshes = static_cast(mesh_list.size()); + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; + for (size_t i = 0; i < pScene->mNumMeshes; i++) + pScene->mMeshes[i] = *it++; + } + + if (!mat_list.empty()) { + std::list::const_iterator it = mat_list.begin(); + + pScene->mNumMaterials = static_cast(mat_list.size()); + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials]; + for (size_t i = 0; i < pScene->mNumMaterials; i++) + pScene->mMaterials[i] = *it++; + } + + if (!light_list.empty()) { + std::list::const_iterator it = light_list.begin(); + + pScene->mNumLights = static_cast(light_list.size()); + pScene->mLights = new aiLight *[pScene->mNumLights]; + for (size_t i = 0; i < pScene->mNumLights; i++) + pScene->mLights[i] = *it++; + } + } + } const aiImporterDesc *X3DImporter::GetInfo() const { @@ -421,6 +473,1741 @@ void readMetadataString(XmlNode &node, X3DNodeElementBase *parent) { } } +void X3DImporter::ParseDirectionalLight(XmlNode &node) { + std::string def, use; + float ambientIntensity = 0; + aiColor3D color(1, 1, 1); + aiVector3D direction(0, 0, -1); + bool global = false; + float intensity = 1; + bool on = true; + X3DNodeElementBase *ne = nullptr; + + //MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + XmlParser::getFloatAttribute(node, "ambientIntensity", ambientIntensity); + //MACRO_ATTRREAD_CHECK_RET("ambientIntensity", ambientIntensity, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_REF("color", color, XML_ReadNode_GetAttrVal_AsCol3f); + MACRO_ATTRREAD_CHECK_REF("direction", direction, XML_ReadNode_GetAttrVal_AsVec3f); + MACRO_ATTRREAD_CHECK_RET("global", global, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("intensity", intensity, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_RET("on", on, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(def, use, ENET_DirectionalLight, ne); + } else { + if (on) { + // create and if needed - define new geometry object. + ne = new X3DNodeNodeElementLight(CX3DImporter_NodeElement::ENET_DirectionalLight, NodeElement_Cur); + if (!def.empty()) + ne->ID = def; + else + ne->ID = "DirectionalLight_" + to_string((size_t)ne); // make random name + + ((X3DNodeNodeElementLight *)ne)->AmbientIntensity = ambientIntensity; + ((X3DNodeNodeElementLight *)ne)->Color = color; + ((X3DNodeNodeElementLight *)ne)->Direction = direction; + ((X3DNodeNodeElementLight *)ne)->Global = global; + ((X3DNodeNodeElementLight *)ne)->Intensity = intensity; + // Assimp want a node with name similar to a light. "Why? I don't no." ) + ParseHelper_Group_Begin(false); + + mNodeElementCur->ID = ne->ID; // assign name to node and return to light element. + ParseHelper_Node_Exit(); + // check for child nodes + if (!mReader->isEmptyElement()) + ParseNode_Metadata(ne, "DirectionalLight"); + else + mNodeElementCur->Child.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(on) + } // if(!use.empty()) else +} + +// +void X3DImporter::ParseNode_Lighting_PointLight() { + std::string def, use; + float ambientIntensity = 0; + aiVector3D attenuation(1, 0, 0); + aiColor3D color(1, 1, 1); + bool global = true; + float intensity = 1; + aiVector3D location(0, 0, 0); + bool on = true; + float radius = 100; + X3DNodeElementBase *ne = nullptr; + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_RET("ambientIntensity", ambientIntensity, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_REF("attenuation", attenuation, XML_ReadNode_GetAttrVal_AsVec3f); + MACRO_ATTRREAD_CHECK_REF("color", color, XML_ReadNode_GetAttrVal_AsCol3f); + MACRO_ATTRREAD_CHECK_RET("global", global, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("intensity", intensity, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_REF("location", location, XML_ReadNode_GetAttrVal_AsVec3f); + MACRO_ATTRREAD_CHECK_RET("on", on, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("radius", radius, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(def, use, ENET_PointLight, ne); + } else { + if (on) { + // create and if needed - define new geometry object. + ne = new X3DNodeNodeElementLight(X3DElemType::ENET_PointLight, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeNodeElementLight *)ne)->AmbientIntensity = ambientIntensity; + ((X3DNodeNodeElementLight *)ne)->Attenuation = attenuation; + ((X3DNodeNodeElementLight *)ne)->Color = color; + ((X3DNodeNodeElementLight *)ne)->Global = global; + ((X3DNodeNodeElementLight *)ne)->Intensity = intensity; + ((X3DNodeNodeElementLight *)ne)->Location = location; + ((X3DNodeNodeElementLight *)ne)->Radius = radius; + // Assimp want a node with name similar to a light. "Why? I don't no." ) + ParseHelper_Group_Begin(false); + // make random name + if (ne->ID.empty()) ne->ID = "PointLight_" + to_string((size_t)ne); + + mNodeElementCur->ID = ne->ID; // assign name to node and return to light element. + ParseHelper_Node_Exit(); + // check for child nodes + if (!mReader->isEmptyElement()) + ParseNode_Metadata(ne, "PointLight"); + else + mNodeElementCur->Child.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(on) + } // if(!use.empty()) else +} + +// +void X3DImporter::ParseNode_Lighting_SpotLight() { + std::string def, use; + float ambientIntensity = 0; + aiVector3D attenuation(1, 0, 0); + float beamWidth = 0.7854f; + aiColor3D color(1, 1, 1); + float cutOffAngle = 1.570796f; + aiVector3D direction(0, 0, -1); + bool global = true; + float intensity = 1; + aiVector3D location(0, 0, 0); + bool on = true; + float radius = 100; + X3DNodeElementBase *ne = nullptr; + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_RET("ambientIntensity", ambientIntensity, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_REF("attenuation", attenuation, XML_ReadNode_GetAttrVal_AsVec3f); + MACRO_ATTRREAD_CHECK_RET("beamWidth", beamWidth, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_REF("color", color, XML_ReadNode_GetAttrVal_AsCol3f); + MACRO_ATTRREAD_CHECK_RET("cutOffAngle", cutOffAngle, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_REF("direction", direction, XML_ReadNode_GetAttrVal_AsVec3f); + MACRO_ATTRREAD_CHECK_RET("global", global, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("intensity", intensity, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_REF("location", location, XML_ReadNode_GetAttrVal_AsVec3f); + MACRO_ATTRREAD_CHECK_RET("on", on, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("radius", radius, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(def, use, ENET_SpotLight, ne); + } else { + if (on) { + // create and if needed - define new geometry object. + ne = new X3DNodeNodeElementLight(X3DElemType::ENET_SpotLight, mNodeElementCur); + if (!def.empty()) + ne->ID = def; + + if (beamWidth > cutOffAngle) + beamWidth = cutOffAngle; + + ((X3DNodeNodeElementLight *)ne)->AmbientIntensity = ambientIntensity; + ((X3DNodeNodeElementLight *)ne)->Attenuation = attenuation; + ((X3DNodeNodeElementLight *)ne)->BeamWidth = beamWidth; + ((X3DNodeNodeElementLight *)ne)->Color = color; + ((X3DNodeNodeElementLight *)ne)->CutOffAngle = cutOffAngle; + ((X3DNodeNodeElementLight *)ne)->Direction = direction; + ((X3DNodeNodeElementLight *)ne)->Global = global; + ((X3DNodeNodeElementLight *)ne)->Intensity = intensity; + ((X3DNodeNodeElementLight *)ne)->Location = location; + ((X3DNodeNodeElementLight *)ne)->Radius = radius; + + // Assimp want a node with name similar to a light. "Why? I don't no." ) + ParseHelper_Group_Begin(false); + // make random name + if (ne->ID.empty()) ne->ID = "SpotLight_" + to_string((size_t)ne); + + mNodeElementCur->ID = ne->ID; // assign name to node and return to light element. + ParseHelper_Node_Exit(); + // check for child nodes + if (!mReader->isEmptyElement()) + ParseNode_Metadata(ne, "SpotLight"); + else + mNodeElementCur->Child.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(on) + } // if(!use.empty()) else +} + +void X3DImporter::ParseNode_Grouping_Group() { + std::string def, use; + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + X3DNodeElementBase *ne = nullptr; + + MACRO_USE_CHECKANDAPPLY(def, use, ENET_Group, ne); + } else { + ParseHelper_Group_Begin(); // create new grouping element and go deeper if node has children. + // at this place new group mode created and made current, so we can name it. + if (!def.empty()) mNodeElementCur->ID = def; + // in grouping set of nodes check X3DMetadataObject is not needed, because it is done in parser function. + + // for empty element exit from node in that place + if (mReader->isEmptyElement()) ParseHelper_Node_Exit(); + } // if(!use.empty()) else +} + +void X3DImporter::ParseNode_Grouping_GroupEnd() { + ParseHelper_Node_Exit(); // go up in scene graph +} + +// +// +// ChildContentModel is the child-node content model corresponding to X3DChildNode, combining all profiles. ChildContentModel can contain most nodes, +// other Grouping nodes, Prototype declarations and ProtoInstances in any order and any combination. When the assigned profile is less than Full, the +// precise palette of legal nodes that are available depends on assigned profile and components. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +// The StaticGroup node contains children nodes which cannot be modified. StaticGroup children are guaranteed to not change, send events, receive events or +// contain any USE references outside the StaticGroup. +void X3DImporter::ParseNode_Grouping_StaticGroup() { + std::string def, use; + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + X3DNodeElementBase *ne = nullptr; + + MACRO_USE_CHECKANDAPPLY(def, use, ENET_Group, ne); + } else { + ParseHelper_Group_Begin(true); // create new grouping element and go deeper if node has children. + // at this place new group mode created and made current, so we can name it. + if (!def.empty()) mNodeElementCur->ID = def; + // in grouping set of nodes check X3DMetadataObject is not needed, because it is done in parser function. + + // for empty element exit from node in that place + if (mReader->isEmptyElement()) ParseHelper_Node_Exit(); + } // if(!use.empty()) else +} + +void X3DImporter::ParseNode_Grouping_StaticGroupEnd() { + ParseHelper_Node_Exit(); // go up in scene graph +} + +// +// +// ChildContentModel is the child-node content model corresponding to X3DChildNode, combining all profiles. ChildContentModel can contain most nodes, +// other Grouping nodes, Prototype declarations and ProtoInstances in any order and any combination. When the assigned profile is less than Full, the +// precise palette of legal nodes that are available depends on assigned profile and components. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +// The Switch grouping node traverses zero or one of the nodes specified in the children field. The whichChoice field specifies the index of the child +// to traverse, with the first child having index 0. If whichChoice is less than zero or greater than the number of nodes in the children field, nothing +// is chosen. +void X3DImporter::ParseNode_Grouping_Switch() { + std::string def, use; + int32_t whichChoice = -1; + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_RET("whichChoice", whichChoice, XML_ReadNode_GetAttrVal_AsI32); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + CX3DImporter_NodeElement *ne; + + MACRO_USE_CHECKANDAPPLY(def, use, ENET_Group, ne); + } else { + ParseHelper_Group_Begin(); // create new grouping element and go deeper if node has children. + // at this place new group mode created and made current, so we can name it. + if (!def.empty()) NodeElement_Cur->ID = def; + + // also set values specific to this type of group + ((CX3DNodeElementGroup *)NodeElement_Cur)->UseChoice = true; + ((CX3DNodeElementGroup *)NodeElement_Cur)->Choice = whichChoice; + // in grouping set of nodes check X3DMetadataObject is not needed, because it is done in parser function. + + // for empty element exit from node in that place + if (mReader->isEmptyElement()) ParseHelper_Node_Exit(); + } // if(!use.empty()) else +} + +void X3DImporter::ParseNode_Grouping_SwitchEnd() { + // just exit from node. Defined choice will be accepted at postprocessing stage. + ParseHelper_Node_Exit(); // go up in scene graph +} + +// +// +// ChildContentModel is the child-node content model corresponding to X3DChildNode, combining all profiles. ChildContentModel can contain most nodes, +// other Grouping nodes, Prototype declarations and ProtoInstances in any order and any combination. When the assigned profile is less than Full, the +// precise palette of legal nodes that are available depends on assigned profile and components. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +// The Transform node is a grouping node that defines a coordinate system for its children that is relative to the coordinate systems of its ancestors. +// Given a 3-dimensional point P and Transform node, P is transformed into point P' in its parent's coordinate system by a series of intermediate +// transformations. In matrix transformation notation, where C (center), SR (scaleOrientation), T (translation), R (rotation), and S (scale) are the +// equivalent transformation matrices, +// P' = T * C * R * SR * S * -SR * -C * P +void X3DImporter::ParseNode_Grouping_Transform() { + aiVector3D center(0, 0, 0); + float rotation[4] = { 0, 0, 1, 0 }; + aiVector3D scale(1, 1, 1); // A value of zero indicates that any child geometry shall not be displayed + float scale_orientation[4] = { 0, 0, 1, 0 }; + aiVector3D translation(0, 0, 0); + aiMatrix4x4 matr, tmatr; + std::string use, def; + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_REF("center", center, XML_ReadNode_GetAttrVal_AsVec3f); + MACRO_ATTRREAD_CHECK_REF("scale", scale, XML_ReadNode_GetAttrVal_AsVec3f); + MACRO_ATTRREAD_CHECK_REF("translation", translation, XML_ReadNode_GetAttrVal_AsVec3f); + if (an == "rotation") { + std::vector tvec; + + XML_ReadNode_GetAttrVal_AsArrF(idx, tvec); + if (tvec.size() != 4) throw DeadlyImportError(": rotation vector must have 4 elements."); + + memcpy(rotation, tvec.data(), sizeof(rotation)); + + continue; + } + + if (an == "scaleOrientation") { + std::vector tvec; + XML_ReadNode_GetAttrVal_AsArrF(idx, tvec); + if (tvec.size() != 4) { + throw DeadlyImportError(": scaleOrientation vector must have 4 elements."); + } + + ::memcpy(scale_orientation, tvec.data(), sizeof(scale_orientation)); + + continue; + } + + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + CX3DImporter_NodeElement *ne(nullptr); + + MACRO_USE_CHECKANDAPPLY(def, use, ENET_Group, ne); + } else { + ParseHelper_Group_Begin(); // create new grouping element and go deeper if node has children. + // at this place new group mode created and made current, so we can name it. + if (!def.empty()) { + NodeElement_Cur->ID = def; + } + + // + // also set values specific to this type of group + // + // calculate transformation matrix + aiMatrix4x4::Translation(translation, matr); // T + aiMatrix4x4::Translation(center, tmatr); // C + matr *= tmatr; + aiMatrix4x4::Rotation(rotation[3], aiVector3D(rotation[0], rotation[1], rotation[2]), tmatr); // R + matr *= tmatr; + aiMatrix4x4::Rotation(scale_orientation[3], aiVector3D(scale_orientation[0], scale_orientation[1], scale_orientation[2]), tmatr); // SR + matr *= tmatr; + aiMatrix4x4::Scaling(scale, tmatr); // S + matr *= tmatr; + aiMatrix4x4::Rotation(-scale_orientation[3], aiVector3D(scale_orientation[0], scale_orientation[1], scale_orientation[2]), tmatr); // -SR + matr *= tmatr; + aiMatrix4x4::Translation(-center, tmatr); // -C + matr *= tmatr; + // and assign it + ((CX3DNodeElementGroup *)mNodeElementCur)->Transformation = matr; + // in grouping set of nodes check X3DMetadataObject is not needed, because it is done in parser function. + + // for empty element exit from node in that place + if (mReader->isEmptyElement()) { + ParseHelper_Node_Exit(); + } + } // if(!use.empty()) else +} + +void X3DImporter::ParseNode_Grouping_TransformEnd() { + ParseHelper_Node_Exit(); // go up in scene graph +} + +void X3DImporter::ParseNode_Geometry2D_Arc2D() { + std::string def, use; + float endAngle = AI_MATH_HALF_PI_F; + float radius = 1; + float startAngle = 0; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_RET("endAngle", endAngle, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_RET("radius", radius, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_RET("startAngle", startAngle, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(def, use, ENET_Arc2D, ne); + } else { + // create and if needed - define new geometry object. + ne = new CX3DImporter_NodeElement_Geometry2D(CX3DImporter_NodeElement::ENET_Arc2D, NodeElement_Cur); + if (!def.empty()) ne->ID = def; + + // create point list of geometry object and convert it to line set. + std::list tlist; + + GeometryHelper_Make_Arc2D(startAngle, endAngle, radius, 10, tlist); ///TODO: IME - AI_CONFIG for NumSeg + GeometryHelper_Extend_PointToLine(tlist, ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices); + ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = 2; + // check for X3DMetadataObject childs. + if (!mReader->isEmptyElement()) + ParseNode_Metadata(ne, "Arc2D"); + else + NodeElement_Cur->Child.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +// The ArcClose node specifies a portion of a circle whose center is at (0,0) and whose angles are measured starting at the positive x-axis and sweeping +// towards the positive y-axis. The end points of the arc specified are connected as defined by the closureType field. The radius field specifies the radius +// of the circle of which the arc is a portion. The arc extends from the startAngle counterclockwise to the endAngle. The value of radius shall be greater +// than zero. The values of startAngle and endAngle shall be in the range [-2pi, 2pi] radians (or the equivalent if a different default angle base unit has +// been specified). If startAngle and endAngle have the same value, a circle is specified and closureType is ignored. If the absolute difference between +// startAngle and endAngle is greater than or equal to 2pi, a complete circle is produced with no chord or radial line(s) drawn from the center. +// A closureType of "PIE" connects the end point to the start point by defining two straight line segments first from the end point to the center and then +// the center to the start point. A closureType of "CHORD" connects the end point to the start point by defining a straight line segment from the end point +// to the start point. Textures are applied individually to each face of the ArcClose2D. On the front (+Z) and back (-Z) faces of the ArcClose2D, when +// viewed from the +Z-axis, the texture is mapped onto each face with the same orientation as if the image were displayed normally in 2D. +void X3DImporter::ParseNode_Geometry2D_ArcClose2D() { + std::string def, use; + std::string closureType("PIE"); + float endAngle = AI_MATH_HALF_PI_F; + float radius = 1; + bool solid = false; + float startAngle = 0; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_RET("closureType", closureType, mReader->getAttributeValue); + MACRO_ATTRREAD_CHECK_RET("endAngle", endAngle, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_RET("radius", radius, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("startAngle", startAngle, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(def, use, ENET_ArcClose2D, ne); + } else { + // create and if needed - define new geometry object. + ne = new CX3DImporter_NodeElement_Geometry2D(CX3DImporter_NodeElement::ENET_ArcClose2D, NodeElement_Cur); + if (!def.empty()) ne->ID = def; + + ((CX3DImporter_NodeElement_Geometry2D *)ne)->Solid = solid; + // create point list of geometry object. + GeometryHelper_Make_Arc2D(startAngle, endAngle, radius, 10, ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices); ///TODO: IME - AI_CONFIG for NumSeg + // add chord or two radiuses only if not a circle was defined + if (!((std::fabs(endAngle - startAngle) >= AI_MATH_TWO_PI_F) || (endAngle == startAngle))) { + std::list &vlist = ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices; // just short alias. + + if ((closureType == "PIE") || (closureType == "\"PIE\"")) + vlist.push_back(aiVector3D(0, 0, 0)); // center point - first radial line + else if ((closureType != "CHORD") && (closureType != "\"CHORD\"")) + Throw_IncorrectAttrValue("closureType"); + + vlist.push_back(*vlist.begin()); // arc first point - chord from first to last point of arc(if CHORD) or second radial line(if PIE). + } + + ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices.size(); + // check for X3DMetadataObject childs. + if (!mReader->isEmptyElement()) + ParseNode_Metadata(ne, "ArcClose2D"); + else + NodeElement_Cur->Child.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +void X3DImporter::ParseNode_Geometry2D_Circle2D() { + std::string def, use; + float radius = 1; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_RET("radius", radius, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(def, use, ENET_Circle2D, ne); + } else { + // create and if needed - define new geometry object. + ne = new CX3DImporter_NodeElement_Geometry2D(CX3DImporter_NodeElement::ENET_Circle2D, NodeElement_Cur); + if (!def.empty()) ne->ID = def; + + // create point list of geometry object and convert it to line set. + std::list tlist; + + GeometryHelper_Make_Arc2D(0, 0, radius, 10, tlist); ///TODO: IME - AI_CONFIG for NumSeg + GeometryHelper_Extend_PointToLine(tlist, ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices); + ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = 2; + // check for X3DMetadataObject childs. + if (!mReader->isEmptyElement()) + ParseNode_Metadata(ne, "Circle2D"); + else + NodeElement_Cur->Child.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +// The Disk2D node specifies a circular disk which is centred at (0, 0) in the local coordinate system. The outerRadius field specifies the radius of the +// outer dimension of the Disk2D. The innerRadius field specifies the inner dimension of the Disk2D. The value of outerRadius shall be greater than zero. +// The value of innerRadius shall be greater than or equal to zero and less than or equal to outerRadius. If innerRadius is zero, the Disk2D is completely +// filled. Otherwise, the area within the innerRadius forms a hole in the Disk2D. If innerRadius is equal to outerRadius, a solid circular line shall +// be drawn using the current line properties. Textures are applied individually to each face of the Disk2D. On the front (+Z) and back (-Z) faces of +// the Disk2D, when viewed from the +Z-axis, the texture is mapped onto each face with the same orientation as if the image were displayed normally in 2D. +void X3DImporter::ParseNode_Geometry2D_Disk2D() { + std::string def, use; + float innerRadius = 0; + float outerRadius = 1; + bool solid = false; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_RET("innerRadius", innerRadius, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_RET("outerRadius", outerRadius, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(def, use, ENET_Disk2D, ne); + } else { + std::list tlist_o, tlist_i; + + if (innerRadius > outerRadius) Throw_IncorrectAttrValue("innerRadius"); + + // create and if needed - define new geometry object. + ne = new CX3DImporter_NodeElement_Geometry2D(CX3DImporter_NodeElement::ENET_Disk2D, NodeElement_Cur); + if (!def.empty()) ne->ID = def; + + // create point list of geometry object. + ///TODO: IME - AI_CONFIG for NumSeg + GeometryHelper_Make_Arc2D(0, 0, outerRadius, 10, tlist_o); // outer circle + if (innerRadius == 0.0f) { // make filled disk + // in tlist_o we already have points of circle. just copy it and sign as polygon. + ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices = tlist_o; + ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = tlist_o.size(); + } else if (innerRadius == outerRadius) { // make circle + // in tlist_o we already have points of circle. convert it to line set. + GeometryHelper_Extend_PointToLine(tlist_o, ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices); + ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = 2; + } else { // make disk + std::list &vlist = ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices; // just short alias. + + GeometryHelper_Make_Arc2D(0, 0, innerRadius, 10, tlist_i); // inner circle + // + // create quad list from two point lists + // + if (tlist_i.size() < 2) throw DeadlyImportError("Disk2D. Not enough points for creating quad list."); // tlist_i and tlist_o has equal size. + + // add all quads except last + for (std::list::iterator it_i = tlist_i.begin(), it_o = tlist_o.begin(); it_i != tlist_i.end();) { + // do not forget - CCW direction + vlist.push_back(*it_i++); // 1st point + vlist.push_back(*it_o++); // 2nd point + vlist.push_back(*it_o); // 3rd point + vlist.push_back(*it_i); // 4th point + } + + // add last quad + vlist.push_back(*tlist_i.end()); // 1st point + vlist.push_back(*tlist_o.end()); // 2nd point + vlist.push_back(*tlist_o.begin()); // 3rd point + vlist.push_back(*tlist_o.begin()); // 4th point + + ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = 4; + } + + ((CX3DImporter_NodeElement_Geometry2D *)ne)->Solid = solid; + // check for X3DMetadataObject childs. + if (!mReader->isEmptyElement()) + ParseNode_Metadata(ne, "Disk2D"); + else + mNodeElementCur->Child.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +void X3DImporter::ParseNode_Geometry2D_Polyline2D() { + std::string def, use; + std::list lineSegments; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_REF("lineSegments", lineSegments, XML_ReadNode_GetAttrVal_AsListVec2f); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(def, use, ENET_Polyline2D, ne); + } else { + // create and if needed - define new geometry object. + ne = new CX3DImporter_NodeElement_Geometry2D(CX3DImporter_NodeElement::ENET_Polyline2D, NodeElement_Cur); + if (!def.empty()) ne->ID = def; + + // + // convert read point list of geometry object to line set. + // + std::list tlist; + + // convert vec2 to vec3 + for (std::list::iterator it2 = lineSegments.begin(); it2 != lineSegments.end(); ++it2) + tlist.push_back(aiVector3D(it2->x, it2->y, 0)); + + // convert point set to line set + GeometryHelper_Extend_PointToLine(tlist, ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices); + ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = 2; + // check for X3DMetadataObject childs. + if (!mReader->isEmptyElement()) + ParseNode_Metadata(ne, "Polyline2D"); + else + NodeElement_Cur->Child.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +void X3DImporter::ParseNode_Geometry2D_Polypoint2D() { + std::string def, use; + std::list point; + CX3DImporter_NodeElement *ne(nullptr); + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_REF("point", point, XML_ReadNode_GetAttrVal_AsListVec2f); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(def, use, ENET_Polypoint2D, ne); + } else { + // create and if needed - define new geometry object. + ne = new CX3DImporter_NodeElement_Geometry2D(CX3DImporter_NodeElement::ENET_Polypoint2D, NodeElement_Cur); + if (!def.empty()) ne->ID = def; + + // convert vec2 to vec3 + for (std::list::iterator it2 = point.begin(); it2 != point.end(); ++it2) { + ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices.push_back(aiVector3D(it2->x, it2->y, 0)); + } + + ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = 1; + // check for X3DMetadataObject childs. + if (!mReader->isEmptyElement()) + ParseNode_Metadata(ne, "Polypoint2D"); + else + NodeElement_Cur->Child.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +void X3DImporter::ParseNode_Geometry2D_Rectangle2D() { + std::string def, use; + aiVector2D size(2, 2); + bool solid = false; + CX3DImporter_NodeElement *ne(nullptr); + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_REF("size", size, XML_ReadNode_GetAttrVal_AsVec2f); + MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(def, use, ENET_Rectangle2D, ne); + } else { + // create and if needed - define new geometry object. + ne = new CX3DImporter_NodeElement_Geometry2D(CX3DImporter_NodeElement::ENET_Rectangle2D, NodeElement_Cur); + if (!def.empty()) ne->ID = def; + + float x1 = -size.x / 2.0f; + float x2 = size.x / 2.0f; + float y1 = -size.y / 2.0f; + float y2 = size.y / 2.0f; + std::list &vlist = ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices; // just short alias. + + vlist.push_back(aiVector3D(x2, y1, 0)); // 1st point + vlist.push_back(aiVector3D(x2, y2, 0)); // 2nd point + vlist.push_back(aiVector3D(x1, y2, 0)); // 3rd point + vlist.push_back(aiVector3D(x1, y1, 0)); // 4th point + ((CX3DImporter_NodeElement_Geometry2D *)ne)->Solid = solid; + ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = 4; + // check for X3DMetadataObject childs. + if (!mReader->isEmptyElement()) + ParseNode_Metadata(ne, "Rectangle2D"); + else + NodeElement_Cur->Child.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +void X3DImporter::ParseNode_Geometry2D_TriangleSet2D() { + std::string def, use; + bool solid = false; + std::list vertices; + CX3DImporter_NodeElement *ne(nullptr); + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_REF("vertices", vertices, XML_ReadNode_GetAttrVal_AsListVec2f); + MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(def, use, ENET_TriangleSet2D, ne); + } else { + if (vertices.size() % 3) throw DeadlyImportError("TriangleSet2D. Not enough points for defining triangle."); + + // create and if needed - define new geometry object. + ne = new CX3DImporter_NodeElement_Geometry2D(CX3DImporter_NodeElement::ENET_TriangleSet2D, NodeElement_Cur); + if (!def.empty()) ne->ID = def; + + // convert vec2 to vec3 + for (std::list::iterator it2 = vertices.begin(); it2 != vertices.end(); ++it2) { + ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices.push_back(aiVector3D(it2->x, it2->y, 0)); + } + + ((CX3DImporter_NodeElement_Geometry2D *)ne)->Solid = solid; + ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = 3; + // check for X3DMetadataObject childs. + if (!mReader->isEmptyElement()) + ParseNode_Metadata(ne, "TriangleSet2D"); + else + NodeElement_Cur->Child.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +// The Box node specifies a rectangular parallelepiped box centred at (0, 0, 0) in the local coordinate system and aligned with the local coordinate axes. +// By default, the box measures 2 units in each dimension, from -1 to +1. The size field specifies the extents of the box along the X-, Y-, and Z-axes +// respectively and each component value shall be greater than zero. +void X3DImporter::ParseNode_Geometry3D_Box() { + std::string def, use; + bool solid = true; + aiVector3D size(2, 2, 2); + CX3DImporter_NodeElement *ne(nullptr); + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_REF("size", size, XML_ReadNode_GetAttrVal_AsVec3f); + MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(def, use, ENET_Box, ne); + } else { + // create and if needed - define new geometry object. + ne = new CX3DImporter_NodeElement_Geometry3D(CX3DImporter_NodeElement::ENET_Box, NodeElement_Cur); + if (!def.empty()) ne->ID = def; + + GeometryHelper_MakeQL_RectParallelepiped(size, ((CX3DImporter_NodeElement_Geometry3D *)ne)->Vertices); // get quad list + ((CX3DImporter_NodeElement_Geometry3D *)ne)->Solid = solid; + ((CX3DImporter_NodeElement_Geometry3D *)ne)->NumIndices = 4; + // check for X3DMetadataObject childs. + if (!mReader->isEmptyElement()) + ParseNode_Metadata(ne, "Box"); + else + NodeElement_Cur->Child.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +void X3DImporter::ParseNode_Geometry3D_Cone() { + std::string use, def; + bool bottom = true; + float bottomRadius = 1; + float height = 2; + bool side = true; + bool solid = true; + CX3DImporter_NodeElement *ne(nullptr); + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("side", side, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("bottom", bottom, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("height", height, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_RET("bottomRadius", bottomRadius, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(def, use, ENET_Cone, ne); + } else { + const unsigned int tess = 30; ///TODO: IME tessellation factor through ai_property + + std::vector tvec; // temp array for vertices. + + // create and if needed - define new geometry object. + ne = new CX3DImporter_NodeElement_Geometry3D(CX3DImporter_NodeElement::ENET_Cone, NodeElement_Cur); + if (!def.empty()) ne->ID = def; + + // make cone or parts according to flags. + if (side) { + StandardShapes::MakeCone(height, 0, bottomRadius, tess, tvec, !bottom); + } else if (bottom) { + StandardShapes::MakeCircle(bottomRadius, tess, tvec); + height = -(height / 2); + for (std::vector::iterator it = tvec.begin(); it != tvec.end(); ++it) + it->y = height; // y - because circle made in oXZ. + } + + // copy data from temp array + for (std::vector::iterator it = tvec.begin(); it != tvec.end(); ++it) + ((CX3DImporter_NodeElement_Geometry3D *)ne)->Vertices.push_back(*it); + + ((CX3DImporter_NodeElement_Geometry3D *)ne)->Solid = solid; + ((CX3DImporter_NodeElement_Geometry3D *)ne)->NumIndices = 3; + // check for X3DMetadataObject childs. + if (!mReader->isEmptyElement()) + ParseNode_Metadata(ne, "Cone"); + else + NodeElement_Cur->Child.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +void X3DImporter::ParseNode_Geometry3D_Cylinder() { + std::string use, def; + bool bottom = true; + float height = 2; + float radius = 1; + bool side = true; + bool solid = true; + bool top = true; + CX3DImporter_NodeElement *ne(nullptr); + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_RET("radius", radius, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("bottom", bottom, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("top", top, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("side", side, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("height", height, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(def, use, ENET_Cylinder, ne); + } else { + const unsigned int tess = 30; ///TODO: IME tessellation factor through ai_property + + std::vector tside; // temp array for vertices of side. + std::vector tcir; // temp array for vertices of circle. + + // create and if needed - define new geometry object. + ne = new CX3DImporter_NodeElement_Geometry3D(CX3DImporter_NodeElement::ENET_Cylinder, NodeElement_Cur); + if (!def.empty()) ne->ID = def; + + // make cilynder or parts according to flags. + if (side) StandardShapes::MakeCone(height, radius, radius, tess, tside, true); + + height /= 2; // height defined for whole cylinder, when creating top and bottom circle we are using just half of height. + if (top || bottom) StandardShapes::MakeCircle(radius, tess, tcir); + // copy data from temp arrays + std::list &vlist = ((CX3DImporter_NodeElement_Geometry3D *)ne)->Vertices; // just short alias. + + for (std::vector::iterator it = tside.begin(); it != tside.end(); ++it) + vlist.push_back(*it); + + if (top) { + for (std::vector::iterator it = tcir.begin(); it != tcir.end(); ++it) { + (*it).y = height; // y - because circle made in oXZ. + vlist.push_back(*it); + } + } // if(top) + + if (bottom) { + for (std::vector::iterator it = tcir.begin(); it != tcir.end(); ++it) { + (*it).y = -height; // y - because circle made in oXZ. + vlist.push_back(*it); + } + } // if(top) + + ((CX3DImporter_NodeElement_Geometry3D *)ne)->Solid = solid; + ((CX3DImporter_NodeElement_Geometry3D *)ne)->NumIndices = 3; + // check for X3DMetadataObject childs. + if (!mReader->isEmptyElement()) + ParseNode_Metadata(ne, "Cylinder"); + else + NodeElement_Cur->Child.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +// +// ColorNormalTexCoordContentModel can contain Color (or ColorRGBA), Normal and TextureCoordinate, in any order. No more than one instance of any single +// node type is allowed. A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +// The ElevationGrid node specifies a uniform rectangular grid of varying height in the Y=0 plane of the local coordinate system. The geometry is described +// by a scalar array of height values that specify the height of a surface above each point of the grid. The xDimension and zDimension fields indicate +// the number of elements of the grid height array in the X and Z directions. Both xDimension and zDimension shall be greater than or equal to zero. +// If either the xDimension or the zDimension is less than two, the ElevationGrid contains no quadrilaterals. +void X3DImporter::ParseNode_Geometry3D_ElevationGrid() { + std::string use, def; + bool ccw = true; + bool colorPerVertex = true; + float creaseAngle = 0; + std::vector height; + bool normalPerVertex = true; + bool solid = true; + int32_t xDimension = 0; + float xSpacing = 1; + int32_t zDimension = 0; + float zSpacing = 1; + CX3DImporter_NodeElement *ne(nullptr); + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("ccw", ccw, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("colorPerVertex", colorPerVertex, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("normalPerVertex", normalPerVertex, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("creaseAngle", creaseAngle, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_REF("height", height, XML_ReadNode_GetAttrVal_AsArrF); + MACRO_ATTRREAD_CHECK_RET("xDimension", xDimension, XML_ReadNode_GetAttrVal_AsI32); + MACRO_ATTRREAD_CHECK_RET("xSpacing", xSpacing, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_RET("zDimension", zDimension, XML_ReadNode_GetAttrVal_AsI32); + MACRO_ATTRREAD_CHECK_RET("zSpacing", zSpacing, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(def, use, ENET_ElevationGrid, ne); + } else { + if ((xSpacing == 0.0f) || (zSpacing == 0.0f)) throw DeadlyImportError("Spacing in must be grater than zero."); + if ((xDimension <= 0) || (zDimension <= 0)) throw DeadlyImportError("Dimension in must be grater than zero."); + if ((size_t)(xDimension * zDimension) != height.size()) Throw_IncorrectAttrValue("Heights count must be equal to \"xDimension * zDimension\""); + + // create and if needed - define new geometry object. + ne = new CX3DImporter_NodeElement_ElevationGrid(CX3DImporter_NodeElement::ENET_ElevationGrid, NodeElement_Cur); + if (!def.empty()) ne->ID = def; + + CX3DImporter_NodeElement_ElevationGrid &grid_alias = *((CX3DImporter_NodeElement_ElevationGrid *)ne); // create alias for conveience + + { // create grid vertices list + std::vector::const_iterator he_it = height.begin(); + + for (int32_t zi = 0; zi < zDimension; zi++) // rows + { + for (int32_t xi = 0; xi < xDimension; xi++) // columns + { + aiVector3D tvec(xSpacing * xi, *he_it, zSpacing * zi); + + grid_alias.Vertices.push_back(tvec); + ++he_it; + } + } + } // END: create grid vertices list + // + // create faces list. In "coordIdx" format + // + // check if we have quads + if ((xDimension < 2) || (zDimension < 2)) // only one element in dimension is set, create line set. + { + ((CX3DImporter_NodeElement_ElevationGrid *)ne)->NumIndices = 2; // will be holded as line set. + for (size_t i = 0, i_e = (grid_alias.Vertices.size() - 1); i < i_e; i++) { + grid_alias.CoordIdx.push_back(static_cast(i)); + grid_alias.CoordIdx.push_back(static_cast(i + 1)); + grid_alias.CoordIdx.push_back(-1); + } + } else // two or more elements in every dimension is set. create quad set. + { + ((CX3DImporter_NodeElement_ElevationGrid *)ne)->NumIndices = 4; + for (int32_t fzi = 0, fzi_e = (zDimension - 1); fzi < fzi_e; fzi++) // rows + { + for (int32_t fxi = 0, fxi_e = (xDimension - 1); fxi < fxi_e; fxi++) // columns + { + // points direction in face. + if (ccw) { + // CCW: + // 3 2 + // 0 1 + grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + fxi); + grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + (fxi + 1)); + grid_alias.CoordIdx.push_back(fzi * xDimension + (fxi + 1)); + grid_alias.CoordIdx.push_back(fzi * xDimension + fxi); + } else { + // CW: + // 0 1 + // 3 2 + grid_alias.CoordIdx.push_back(fzi * xDimension + fxi); + grid_alias.CoordIdx.push_back(fzi * xDimension + (fxi + 1)); + grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + (fxi + 1)); + grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + fxi); + } // if(ccw) else + + grid_alias.CoordIdx.push_back(-1); + } // for(int32_t fxi = 0, fxi_e = (xDimension - 1); fxi < fxi_e; fxi++) + } // for(int32_t fzi = 0, fzi_e = (zDimension - 1); fzi < fzi_e; fzi++) + } // if((xDimension < 2) || (zDimension < 2)) else + + grid_alias.ColorPerVertex = colorPerVertex; + grid_alias.NormalPerVertex = normalPerVertex; + grid_alias.CreaseAngle = creaseAngle; + grid_alias.Solid = solid; + // check for child nodes + if (!mReader->isEmptyElement()) { + ParseHelper_Node_Enter(ne); + MACRO_NODECHECK_LOOPBEGIN("ElevationGrid"); + // check for X3DComposedGeometryNodes + if (XML_CheckNode_NameEqual("Color")) { + ParseNode_Rendering_Color(); + continue; + } + if (XML_CheckNode_NameEqual("ColorRGBA")) { + ParseNode_Rendering_ColorRGBA(); + continue; + } + if (XML_CheckNode_NameEqual("Normal")) { + ParseNode_Rendering_Normal(); + continue; + } + if (XML_CheckNode_NameEqual("TextureCoordinate")) { + ParseNode_Texturing_TextureCoordinate(); + continue; + } + // check for X3DMetadataObject + if (!ParseHelper_CheckRead_X3DMetadataObject()) XML_CheckNode_SkipUnsupported("ElevationGrid"); + + MACRO_NODECHECK_LOOPEND("ElevationGrid"); + ParseHelper_Node_Exit(); + } // if(!mReader->isEmptyElement()) + else { + NodeElement_Cur->Child.push_back(ne); // add made object as child to current element + } // if(!mReader->isEmptyElement()) else + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +template +static void GeometryHelper_Extrusion_CurveIsClosed(std::vector &pCurve, const bool pDropTail, const bool pRemoveLastPoint, bool &pCurveIsClosed) { + size_t cur_sz = pCurve.size(); + + pCurveIsClosed = false; + // for curve with less than four points checking is have no sense, + if (cur_sz < 4) return; + + for (size_t s = 3, s_e = cur_sz; s < s_e; s++) { + // search for first point of duplicated part. + if (pCurve[0] == pCurve[s]) { + bool found = true; + + // check if tail(indexed by b2) is duplicate of head(indexed by b1). + for (size_t b1 = 1, b2 = (s + 1); b2 < cur_sz; b1++, b2++) { + if (pCurve[b1] != pCurve[b2]) { // points not match: clear flag and break loop. + found = false; + + break; + } + } // for(size_t b1 = 1, b2 = (s + 1); b2 < cur_sz; b1++, b2++) + + // if duplicate tail is found then drop or not it depending on flags. + if (found) { + pCurveIsClosed = true; + if (pDropTail) { + if (!pRemoveLastPoint) s++; // prepare value for iterator's arithmetics. + + pCurve.erase(pCurve.begin() + s, pCurve.end()); // remove tail + } + + break; + } // if(found) + } // if(pCurve[0] == pCurve[s]) + } // for(size_t s = 3, s_e = (cur_sz - 1); s < s_e; s++) +} + +static aiVector3D GeometryHelper_Extrusion_GetNextY(const size_t pSpine_PointIdx, const std::vector &pSpine, const bool pSpine_Closed) { + const size_t spine_idx_last = pSpine.size() - 1; + aiVector3D tvec; + + if ((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last)) // at first special cases + { + if (pSpine_Closed) { // If the spine curve is closed: The SCP for the first and last points is the same and is found using (spine[1] - spine[n - 2]) to compute the Y-axis. + // As we even for closed spine curve last and first point in pSpine are not the same: duplicates(spine[n - 1] which are equivalent to spine[0]) + // in tail are removed. + // So, last point in pSpine is a spine[n - 2] + tvec = pSpine[1] - pSpine[spine_idx_last]; + } else if (pSpine_PointIdx == 0) { // The Y-axis used for the first point is the vector from spine[0] to spine[1] + tvec = pSpine[1] - pSpine[0]; + } else { // The Y-axis used for the last point it is the vector from spine[n-2] to spine[n-1]. In our case(see above about dropping tail) spine[n - 1] is + // the spine[0]. + tvec = pSpine[spine_idx_last] - pSpine[spine_idx_last - 1]; + } + } // if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last)) + else { // For all points other than the first or last: The Y-axis for spine[i] is found by normalizing the vector defined by (spine[i+1] - spine[i-1]). + tvec = pSpine[pSpine_PointIdx + 1] - pSpine[pSpine_PointIdx - 1]; + } // if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last)) else + + return tvec.Normalize(); +} + +static aiVector3D GeometryHelper_Extrusion_GetNextZ(const size_t pSpine_PointIdx, const std::vector &pSpine, const bool pSpine_Closed, + const aiVector3D pVecZ_Prev) { + const aiVector3D zero_vec(0); + const size_t spine_idx_last = pSpine.size() - 1; + + aiVector3D tvec; + + // at first special cases + if (pSpine.size() < 3) // spine have not enough points for vector calculations. + { + tvec.Set(0, 0, 1); + } else if (pSpine_PointIdx == 0) // special case: first point + { + if (pSpine_Closed) // for calculating use previous point in curve s[n - 2]. In list it's a last point, because point s[n - 1] was removed as duplicate. + { + tvec = (pSpine[1] - pSpine[0]) ^ (pSpine[spine_idx_last] - pSpine[0]); + } else // for not closed curve first and next point(s[0] and s[1]) has the same vector Z. + { + bool found = false; + + // As said: "If the Z-axis of the first point is undefined (because the spine is not closed and the first two spine segments are collinear) + // then the Z-axis for the first spine point with a defined Z-axis is used." + // Walk through spine and find Z. + for (size_t next_point = 2; (next_point <= spine_idx_last) && !found; next_point++) { + // (pSpine[2] - pSpine[1]) ^ (pSpine[0] - pSpine[1]) + tvec = (pSpine[next_point] - pSpine[next_point - 1]) ^ (pSpine[next_point - 2] - pSpine[next_point - 1]); + found = !tvec.Equal(zero_vec); + } + + // if entire spine are collinear then use OZ axis. + if (!found) tvec.Set(0, 0, 1); + } // if(pSpine_Closed) else + } // else if(pSpine_PointIdx == 0) + else if (pSpine_PointIdx == spine_idx_last) // special case: last point + { + if (pSpine_Closed) { // do not forget that real last point s[n - 1] is removed as duplicated. And in this case we are calculating vector Z for point s[n - 2]. + tvec = (pSpine[0] - pSpine[pSpine_PointIdx]) ^ (pSpine[pSpine_PointIdx - 1] - pSpine[pSpine_PointIdx]); + // if taken spine vectors are collinear then use previous vector Z. + if (tvec.Equal(zero_vec)) tvec = pVecZ_Prev; + } else { // vector Z for last point of not closed curve is previous vector Z. + tvec = pVecZ_Prev; + } + } else // regular point + { + tvec = (pSpine[pSpine_PointIdx + 1] - pSpine[pSpine_PointIdx]) ^ (pSpine[pSpine_PointIdx - 1] - pSpine[pSpine_PointIdx]); + // if taken spine vectors are collinear then use previous vector Z. + if (tvec.Equal(zero_vec)) tvec = pVecZ_Prev; + } + + // After determining the Z-axis, its dot product with the Z-axis of the previous spine point is computed. If this value is negative, the Z-axis + // is flipped (multiplied by -1). + if ((tvec * pVecZ_Prev) < 0) tvec = -tvec; + + return tvec.Normalize(); +} + +// +void X3DImporter::ParseNode_Geometry3D_Extrusion() { + std::string use, def; + bool beginCap = true; + bool ccw = true; + bool convex = true; + float creaseAngle = 0; + std::vector crossSection; + bool endCap = true; + std::vector orientation; + std::vector scale; + bool solid = true; + std::vector spine; + CX3DImporter_NodeElement *ne(nullptr); + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_RET("beginCap", beginCap, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("ccw", ccw, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("convex", convex, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("creaseAngle", creaseAngle, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_REF("crossSection", crossSection, XML_ReadNode_GetAttrVal_AsArrVec2f); + MACRO_ATTRREAD_CHECK_RET("endCap", endCap, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_REF("orientation", orientation, XML_ReadNode_GetAttrVal_AsArrF); + MACRO_ATTRREAD_CHECK_REF("scale", scale, XML_ReadNode_GetAttrVal_AsArrVec2f); + MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_REF("spine", spine, XML_ReadNode_GetAttrVal_AsArrVec3f); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(def, use, ENET_Extrusion, ne); + } else { + // + // check if default values must be assigned + // + if (spine.size() == 0) { + spine.resize(2); + spine[0].Set(0, 0, 0), spine[1].Set(0, 1, 0); + } else if (spine.size() == 1) { + throw DeadlyImportError("ParseNode_Geometry3D_Extrusion. Spine must have at least two points."); + } + + if (crossSection.size() == 0) { + crossSection.resize(5); + crossSection[0].Set(1, 1), crossSection[1].Set(1, -1), crossSection[2].Set(-1, -1), crossSection[3].Set(-1, 1), crossSection[4].Set(1, 1); + } + + { // orientation + size_t ori_size = orientation.size() / 4; + + if (ori_size < spine.size()) { + float add_ori[4]; // values that will be added + + if (ori_size == 1) // if "orientation" has one element(means one MFRotation with four components) then use it value for all spine points. + { + add_ori[0] = orientation[0], add_ori[1] = orientation[1], add_ori[2] = orientation[2], add_ori[3] = orientation[3]; + } else // else - use default values + { + add_ori[0] = 0, add_ori[1] = 0, add_ori[2] = 1, add_ori[3] = 0; + } + + orientation.reserve(spine.size() * 4); + for (size_t i = 0, i_e = (spine.size() - ori_size); i < i_e; i++) + orientation.push_back(add_ori[0]), orientation.push_back(add_ori[1]), orientation.push_back(add_ori[2]), orientation.push_back(add_ori[3]); + } + + if (orientation.size() % 4) throw DeadlyImportError("Attribute \"orientation\" in must has multiple four quantity of numbers."); + } // END: orientation + + { // scale + if (scale.size() < spine.size()) { + aiVector2D add_sc; + + if (scale.size() == 1) // if "scale" has one element then use it value for all spine points. + add_sc = scale[0]; + else // else - use default values + add_sc.Set(1, 1); + + scale.reserve(spine.size()); + for (size_t i = 0, i_e = (spine.size() - scale.size()); i < i_e; i++) + scale.push_back(add_sc); + } + } // END: scale + // + // create and if needed - define new geometry object. + // + ne = new CX3DImporter_NodeElement_IndexedSet(CX3DImporter_NodeElement::ENET_Extrusion, NodeElement_Cur); + if (!def.empty()) ne->ID = def; + + CX3DImporter_NodeElement_IndexedSet &ext_alias = *((CX3DImporter_NodeElement_IndexedSet *)ne); // create alias for conveience + // assign part of input data + ext_alias.CCW = ccw; + ext_alias.Convex = convex; + ext_alias.CreaseAngle = creaseAngle; + ext_alias.Solid = solid; + + // + // How we done it at all? + // 1. At first we will calculate array of basises for every point in spine(look SCP in ISO-dic). Also "orientation" vector + // are applied vor every basis. + // 2. After that we can create array of point sets: which are scaled, transferred to basis of relative basis and at final translated to real position + // using relative spine point. + // 3. Next step is creating CoordIdx array(do not forget "-1" delimiter). While creating CoordIdx also created faces for begin and end caps, if + // needed. While createing CootdIdx is taking in account CCW flag. + // 4. The last step: create Vertices list. + // + bool spine_closed; // flag: true if spine curve is closed. + bool cross_closed; // flag: true if cross curve is closed. + std::vector basis_arr; // array of basises. ROW_a - X, ROW_b - Y, ROW_c - Z. + std::vector> pointset_arr; // array of point sets: cross curves. + + // detect closed curves + GeometryHelper_Extrusion_CurveIsClosed(crossSection, true, true, cross_closed); // true - drop tail, true - remove duplicate end. + GeometryHelper_Extrusion_CurveIsClosed(spine, true, true, spine_closed); // true - drop tail, true - remove duplicate end. + // If both cap are requested and spine curve is closed then we can make only one cap. Because second cap will be the same surface. + if (spine_closed) { + beginCap |= endCap; + endCap = false; + } + + { // 1. Calculate array of basises. + aiMatrix4x4 rotmat; + aiVector3D vecX(0), vecY(0), vecZ(0); + + basis_arr.resize(spine.size()); + for (size_t i = 0, i_e = spine.size(); i < i_e; i++) { + aiVector3D tvec; + + // get axises of basis. + vecY = GeometryHelper_Extrusion_GetNextY(i, spine, spine_closed); + vecZ = GeometryHelper_Extrusion_GetNextZ(i, spine, spine_closed, vecZ); + vecX = (vecY ^ vecZ).Normalize(); + // get rotation matrix and apply "orientation" to basis + aiMatrix4x4::Rotation(orientation[i * 4 + 3], aiVector3D(orientation[i * 4], orientation[i * 4 + 1], orientation[i * 4 + 2]), rotmat); + tvec = vecX, tvec *= rotmat, basis_arr[i].a1 = tvec.x, basis_arr[i].a2 = tvec.y, basis_arr[i].a3 = tvec.z; + tvec = vecY, tvec *= rotmat, basis_arr[i].b1 = tvec.x, basis_arr[i].b2 = tvec.y, basis_arr[i].b3 = tvec.z; + tvec = vecZ, tvec *= rotmat, basis_arr[i].c1 = tvec.x, basis_arr[i].c2 = tvec.y, basis_arr[i].c3 = tvec.z; + } // for(size_t i = 0, i_e = spine.size(); i < i_e; i++) + } // END: 1. Calculate array of basises + + { // 2. Create array of point sets. + aiMatrix4x4 scmat; + std::vector tcross(crossSection.size()); + + pointset_arr.resize(spine.size()); + for (size_t spi = 0, spi_e = spine.size(); spi < spi_e; spi++) { + aiVector3D tc23vec; + + tc23vec.Set(scale[spi].x, 0, scale[spi].y); + aiMatrix4x4::Scaling(tc23vec, scmat); + for (size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; cri++) { + aiVector3D tvecX, tvecY, tvecZ; + + tc23vec.Set(crossSection[cri].x, 0, crossSection[cri].y); + // apply scaling to point + tcross[cri] = scmat * tc23vec; + // + // transfer point to new basis + // calculate coordinate in new basis + tvecX.Set(basis_arr[spi].a1, basis_arr[spi].a2, basis_arr[spi].a3), tvecX *= tcross[cri].x; + tvecY.Set(basis_arr[spi].b1, basis_arr[spi].b2, basis_arr[spi].b3), tvecY *= tcross[cri].y; + tvecZ.Set(basis_arr[spi].c1, basis_arr[spi].c2, basis_arr[spi].c3), tvecZ *= tcross[cri].z; + // apply new coordinates and translate it to spine point. + tcross[cri] = tvecX + tvecY + tvecZ + spine[spi]; + } // for(size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; i++) + + pointset_arr[spi] = tcross; // store transferred point set + } // for(size_t spi = 0, spi_e = spine.size(); spi < spi_e; i++) + } // END: 2. Create array of point sets. + + { // 3. Create CoordIdx. + // add caps if needed + if (beginCap) { + // add cap as polygon. vertices of cap are places at begin, so just add numbers from zero. + for (size_t i = 0, i_e = crossSection.size(); i < i_e; i++) + ext_alias.CoordIndex.push_back(static_cast(i)); + + // add delimiter + ext_alias.CoordIndex.push_back(-1); + } // if(beginCap) + + if (endCap) { + // add cap as polygon. vertices of cap are places at end, as for beginCap use just sequence of numbers but with offset. + size_t beg = (pointset_arr.size() - 1) * crossSection.size(); + + for (size_t i = beg, i_e = (beg + crossSection.size()); i < i_e; i++) + ext_alias.CoordIndex.push_back(static_cast(i)); + + // add delimiter + ext_alias.CoordIndex.push_back(-1); + } // if(beginCap) + + // add quads + for (size_t spi = 0, spi_e = (spine.size() - 1); spi <= spi_e; spi++) { + const size_t cr_sz = crossSection.size(); + const size_t cr_last = crossSection.size() - 1; + + size_t right_col; // hold index basis for points of quad placed in right column; + + if (spi != spi_e) + right_col = spi + 1; + else if (spine_closed) // if spine curve is closed then one more quad is needed: between first and last points of curve. + right_col = 0; + else + break; // if spine curve is not closed then break the loop, because spi is out of range for that type of spine. + + for (size_t cri = 0; cri < cr_sz; cri++) { + if (cri != cr_last) { + MACRO_FACE_ADD_QUAD(ccw, ext_alias.CoordIndex, + static_cast(spi * cr_sz + cri), + static_cast(right_col * cr_sz + cri), + static_cast(right_col * cr_sz + cri + 1), + static_cast(spi * cr_sz + cri + 1)); + // add delimiter + ext_alias.CoordIndex.push_back(-1); + } else if (cross_closed) // if cross curve is closed then one more quad is needed: between first and last points of curve. + { + MACRO_FACE_ADD_QUAD(ccw, ext_alias.CoordIndex, + static_cast(spi * cr_sz + cri), + static_cast(right_col * cr_sz + cri), + static_cast(right_col * cr_sz + 0), + static_cast(spi * cr_sz + 0)); + // add delimiter + ext_alias.CoordIndex.push_back(-1); + } + } // for(size_t cri = 0; cri < cr_sz; cri++) + } // for(size_t spi = 0, spi_e = (spine.size() - 2); spi < spi_e; spi++) + } // END: 3. Create CoordIdx. + + { // 4. Create vertices list. + // just copy all vertices + for (size_t spi = 0, spi_e = spine.size(); spi < spi_e; spi++) { + for (size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; cri++) { + ext_alias.Vertices.push_back(pointset_arr[spi][cri]); + } + } + } // END: 4. Create vertices list. + //PrintVectorSet("Ext. CoordIdx", ext_alias.CoordIndex); + //PrintVectorSet("Ext. Vertices", ext_alias.Vertices); + // check for child nodes + if (!mReader->isEmptyElement()) + ParseNode_Metadata(ne, "Extrusion"); + else + NodeElement_Cur->Child.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +// +// ComposedGeometryContentModel is the child-node content model corresponding to X3DComposedGeometryNodes. It can contain Color (or ColorRGBA), Coordinate, +// Normal and TextureCoordinate, in any order. No more than one instance of these nodes is allowed. Multiple VertexAttribute (FloatVertexAttribute, +// Matrix3VertexAttribute, Matrix4VertexAttribute) nodes can also be contained. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +void X3DImporter::ParseNode_Geometry3D_IndexedFaceSet() { + std::string use, def; + bool ccw = true; + std::vector colorIndex; + bool colorPerVertex = true; + bool convex = true; + std::vector coordIndex; + float creaseAngle = 0; + std::vector normalIndex; + bool normalPerVertex = true; + bool solid = true; + std::vector texCoordIndex; + CX3DImporter_NodeElement *ne(nullptr); + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_RET("ccw", ccw, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_REF("colorIndex", colorIndex, XML_ReadNode_GetAttrVal_AsArrI32); + MACRO_ATTRREAD_CHECK_RET("colorPerVertex", colorPerVertex, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("convex", convex, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_REF("coordIndex", coordIndex, XML_ReadNode_GetAttrVal_AsArrI32); + MACRO_ATTRREAD_CHECK_RET("creaseAngle", creaseAngle, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_REF("normalIndex", normalIndex, XML_ReadNode_GetAttrVal_AsArrI32); + MACRO_ATTRREAD_CHECK_RET("normalPerVertex", normalPerVertex, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_CHECK_REF("texCoordIndex", texCoordIndex, XML_ReadNode_GetAttrVal_AsArrI32); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(def, use, ENET_IndexedFaceSet, ne); + } else { + // check data + if (coordIndex.size() == 0) throw DeadlyImportError("IndexedFaceSet must contain not empty \"coordIndex\" attribute."); + + // create and if needed - define new geometry object. + ne = new CX3DImporter_NodeElement_IndexedSet(CX3DImporter_NodeElement::ENET_IndexedFaceSet, NodeElement_Cur); + if (!def.empty()) ne->ID = def; + + CX3DImporter_NodeElement_IndexedSet &ne_alias = *((CX3DImporter_NodeElement_IndexedSet *)ne); + + ne_alias.CCW = ccw; + ne_alias.ColorIndex = colorIndex; + ne_alias.ColorPerVertex = colorPerVertex; + ne_alias.Convex = convex; + ne_alias.CoordIndex = coordIndex; + ne_alias.CreaseAngle = creaseAngle; + ne_alias.NormalIndex = normalIndex; + ne_alias.NormalPerVertex = normalPerVertex; + ne_alias.Solid = solid; + ne_alias.TexCoordIndex = texCoordIndex; + // check for child nodes + if (!mReader->isEmptyElement()) { + ParseHelper_Node_Enter(ne); + MACRO_NODECHECK_LOOPBEGIN("IndexedFaceSet"); + // check for X3DComposedGeometryNodes + if (XML_CheckNode_NameEqual("Color")) { + ParseNode_Rendering_Color(); + continue; + } + if (XML_CheckNode_NameEqual("ColorRGBA")) { + ParseNode_Rendering_ColorRGBA(); + continue; + } + if (XML_CheckNode_NameEqual("Coordinate")) { + ParseNode_Rendering_Coordinate(); + continue; + } + if (XML_CheckNode_NameEqual("Normal")) { + ParseNode_Rendering_Normal(); + continue; + } + if (XML_CheckNode_NameEqual("TextureCoordinate")) { + ParseNode_Texturing_TextureCoordinate(); + continue; + } + // check for X3DMetadataObject + if (!ParseHelper_CheckRead_X3DMetadataObject()) XML_CheckNode_SkipUnsupported("IndexedFaceSet"); + + MACRO_NODECHECK_LOOPEND("IndexedFaceSet"); + ParseHelper_Node_Exit(); + } // if(!mReader->isEmptyElement()) + else { + NodeElement_Cur->Child.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +void X3DImporter::ParseNode_Geometry3D_Sphere() { + std::string use, def; + ai_real radius = 1; + bool solid = true; + CX3DImporter_NodeElement *ne(nullptr); + + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); + MACRO_ATTRREAD_CHECK_RET("radius", radius, XML_ReadNode_GetAttrVal_AsFloat); + MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_LOOPEND; + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(def, use, ENET_Sphere, ne); + } else { + const unsigned int tess = 3; ///TODO: IME tessellation factor through ai_property + + std::vector tlist; + + // create and if needed - define new geometry object. + ne = new CX3DImporter_NodeElement_Geometry3D(CX3DImporter_NodeElement::ENET_Sphere, NodeElement_Cur); + if (!def.empty()) ne->ID = def; + + StandardShapes::MakeSphere(tess, tlist); + // copy data from temp array and apply scale + for (std::vector::iterator it = tlist.begin(); it != tlist.end(); ++it) { + ((CX3DImporter_NodeElement_Geometry3D *)ne)->Vertices.push_back(*it * radius); + } + + ((CX3DImporter_NodeElement_Geometry3D *)ne)->Solid = solid; + ((CX3DImporter_NodeElement_Geometry3D *)ne)->NumIndices = 3; + // check for X3DMetadataObject childs. + if (!mReader->isEmptyElement()) + ParseNode_Metadata(ne, "Sphere"); + else + NodeElement_Cur->Child.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + + + void X3DImporter::readMetadataObject(XmlNode &node) { const std::string &name = node.name(); if (name == "MetadataBoolean") { @@ -438,6 +2225,672 @@ void X3DImporter::readMetadataObject(XmlNode &node) { } } -} // namespace Assimp + +aiMatrix4x4 PostprocessHelper_Matrix_GlobalToCurrent() { + X3DNodeElementBase *cur_node = nullptr; + std::list matr; + aiMatrix4x4 out_matr; + + // starting walk from current element to root + cur_node = cur_node; + if (cur_node != nullptr) { + do { + // if cur_node is group then store group transformation matrix in list. + if (cur_node->Type == X3DNodeElementBase::ENET_Group) matr.push_back(((X3DNodeElementBase *)cur_node)->Transformation); + + cur_node = cur_node->Parent; + } while (cur_node != nullptr); + } + + // multiplicate all matrices in reverse order + for (std::list::reverse_iterator rit = matr.rbegin(); rit != matr.rend(); ++rit) + out_matr = out_matr * (*rit); + + return out_matr; +} + +void X3DImporter::PostprocessHelper_CollectMetadata(const CX3DImporter_NodeElement &pNodeElement, std::list &pList) const { + // walk through childs and find for metadata. + for (std::list::const_iterator el_it = pNodeElement.Child.begin(); el_it != pNodeElement.Child.end(); ++el_it) { + if (((*el_it)->Type == X3DElemType::ENET_MetaBoolean) || ((*el_it)->Type == X3DElemType::ENET_MetaDouble) || + ((*el_it)->Type == X3DElemType::ENET_MetaFloat) || ((*el_it)->Type == X3DElemType::ENET_MetaInteger) || + ((*el_it)->Type == X3DElemType::ENET_MetaString)) { + pList.push_back(*el_it); + } else if ((*el_it)->Type == X3DElemType::ENET_MetaSet) { + PostprocessHelper_CollectMetadata(**el_it, pList); + } + } // for(std::list::const_iterator el_it = pNodeElement.Child.begin(); el_it != pNodeElement.Child.end(); el_it++) +} + +bool X3DImporter::PostprocessHelper_ElementIsMetadata(const CX3DImporter_NodeElement::EType pType) const { + if ((pType == X3DNodeElementBase::ENET_MetaBoolean) || (pType == X3DElemType::ENET_MetaDouble) || + (pType == X3DElemType::ENET_MetaFloat) || (pType == X3DElemType::ENET_MetaInteger) || + (pType == X3DElemType::ENET_MetaString) || (pType == X3DElemType::ENET_MetaSet)) { + return true; + } + return false; +} + +bool X3DImporter::PostprocessHelper_ElementIsMesh(const CX3DImporter_NodeElement::EType pType) const { + if ((pType == X3DElemType::ENET_Arc2D) || (pType == X3DElemType::ENET_ArcClose2D) || + (pType == X3DElemType::ENET_Box) || (pType == X3DElemType::ENET_Circle2D) || + (pType == X3DElemType::ENET_Cone) || (pType == X3DElemType::ENET_Cylinder) || + (pType == X3DElemType::ENET_Disk2D) || (pType == X3DElemType::ENET_ElevationGrid) || + (pType == X3DElemType::ENET_Extrusion) || (pType == X3DElemType::ENET_IndexedFaceSet) || + (pType == X3DElemType::ENET_IndexedLineSet) || (pType == X3DElemType::ENET_IndexedTriangleFanSet) || + (pType == X3DElemType::ENET_IndexedTriangleSet) || (pType == X3DElemType::ENET_IndexedTriangleStripSet) || + (pType == X3DElemType::ENET_PointSet) || (pType == X3DElemType::ENET_LineSet) || + (pType == X3DElemType::ENET_Polyline2D) || (pType == X3DElemType::ENET_Polypoint2D) || + (pType == X3DElemType::ENET_Rectangle2D) || (pType == X3DElemType::ENET_Sphere) || + (pType == X3DElemType::ENET_TriangleFanSet) || (pType == X3DElemType::ENET_TriangleSet) || + (pType == X3DElemType::ENET_TriangleSet2D) || (pType == X3DElemType::ENET_TriangleStripSet)) { + return true; + } else { + return false; + } +} + +void X3DImporter::Postprocess_BuildLight(const CX3DImporter_NodeElement &pNodeElement, std::list &pSceneLightList) const { + const CX3DImporter_NodeElement_Light &ne = *((CX3DImporter_NodeElement_Light *)&pNodeElement); + aiMatrix4x4 transform_matr = PostprocessHelper_Matrix_GlobalToCurrent(); + aiLight *new_light = new aiLight; + + new_light->mName = ne.ID; + new_light->mColorAmbient = ne.Color * ne.AmbientIntensity; + new_light->mColorDiffuse = ne.Color * ne.Intensity; + new_light->mColorSpecular = ne.Color * ne.Intensity; + switch (pNodeElement.Type) { + case CX3DImporter_NodeElement::ENET_DirectionalLight: + new_light->mType = aiLightSource_DIRECTIONAL; + new_light->mDirection = ne.Direction, new_light->mDirection *= transform_matr; + + break; + case CX3DImporter_NodeElement::ENET_PointLight: + new_light->mType = aiLightSource_POINT; + new_light->mPosition = ne.Location, new_light->mPosition *= transform_matr; + new_light->mAttenuationConstant = ne.Attenuation.x; + new_light->mAttenuationLinear = ne.Attenuation.y; + new_light->mAttenuationQuadratic = ne.Attenuation.z; + + break; + case CX3DImporter_NodeElement::ENET_SpotLight: + new_light->mType = aiLightSource_SPOT; + new_light->mPosition = ne.Location, new_light->mPosition *= transform_matr; + new_light->mDirection = ne.Direction, new_light->mDirection *= transform_matr; + new_light->mAttenuationConstant = ne.Attenuation.x; + new_light->mAttenuationLinear = ne.Attenuation.y; + new_light->mAttenuationQuadratic = ne.Attenuation.z; + new_light->mAngleInnerCone = ne.BeamWidth; + new_light->mAngleOuterCone = ne.CutOffAngle; + + break; + default: + throw DeadlyImportError("Postprocess_BuildLight. Unknown type of light: " + to_string(pNodeElement.Type) + "."); + } + + pSceneLightList.push_back(new_light); +} + +void X3DImporter::Postprocess_BuildMaterial(const CX3DImporter_NodeElement &pNodeElement, aiMaterial **pMaterial) const { + // check argument + if (pMaterial == nullptr) throw DeadlyImportError("Postprocess_BuildMaterial. pMaterial is nullptr."); + if (*pMaterial != nullptr) throw DeadlyImportError("Postprocess_BuildMaterial. *pMaterial must be nullptr."); + + *pMaterial = new aiMaterial; + aiMaterial &taimat = **pMaterial; // creating alias for convenience. + + // at this point pNodeElement point to node. Walk through childs and add all stored data. + for (std::list::const_iterator el_it = pNodeElement.Child.begin(); el_it != pNodeElement.Child.end(); ++el_it) { + if ((*el_it)->Type == CX3DImporter_NodeElement::ENET_Material) { + aiColor3D tcol3; + float tvalf; + CX3DImporter_NodeElement_Material &tnemat = *((CX3DImporter_NodeElement_Material *)*el_it); + + tcol3.r = tnemat.AmbientIntensity, tcol3.g = tnemat.AmbientIntensity, tcol3.b = tnemat.AmbientIntensity; + taimat.AddProperty(&tcol3, 1, AI_MATKEY_COLOR_AMBIENT); + taimat.AddProperty(&tnemat.DiffuseColor, 1, AI_MATKEY_COLOR_DIFFUSE); + taimat.AddProperty(&tnemat.EmissiveColor, 1, AI_MATKEY_COLOR_EMISSIVE); + taimat.AddProperty(&tnemat.SpecularColor, 1, AI_MATKEY_COLOR_SPECULAR); + tvalf = 1; + taimat.AddProperty(&tvalf, 1, AI_MATKEY_SHININESS_STRENGTH); + taimat.AddProperty(&tnemat.Shininess, 1, AI_MATKEY_SHININESS); + tvalf = 1.0f - tnemat.Transparency; + taimat.AddProperty(&tvalf, 1, AI_MATKEY_OPACITY); + } // if((*el_it)->Type == CX3DImporter_NodeElement::ENET_Material) + else if ((*el_it)->Type == CX3DImporter_NodeElement::ENET_ImageTexture) { + CX3DImporter_NodeElement_ImageTexture &tnetex = *((CX3DImporter_NodeElement_ImageTexture *)*el_it); + aiString url_str(tnetex.URL.c_str()); + int mode = aiTextureOp_Multiply; + + taimat.AddProperty(&url_str, AI_MATKEY_TEXTURE_DIFFUSE(0)); + taimat.AddProperty(&tnetex.RepeatS, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0)); + taimat.AddProperty(&tnetex.RepeatT, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0)); + taimat.AddProperty(&mode, 1, AI_MATKEY_TEXOP_DIFFUSE(0)); + } // else if((*el_it)->Type == CX3DImporter_NodeElement::ENET_ImageTexture) + else if ((*el_it)->Type == CX3DImporter_NodeElement::ENET_TextureTransform) { + aiUVTransform trans; + CX3DImporter_NodeElement_TextureTransform &tnetextr = *((CX3DImporter_NodeElement_TextureTransform *)*el_it); + + trans.mTranslation = tnetextr.Translation - tnetextr.Center; + trans.mScaling = tnetextr.Scale; + trans.mRotation = tnetextr.Rotation; + taimat.AddProperty(&trans, 1, AI_MATKEY_UVTRANSFORM_DIFFUSE(0)); + } // else if((*el_it)->Type == CX3DImporter_NodeElement::ENET_TextureTransform) + } // for(std::list::const_iterator el_it = pNodeElement.Child.begin(); el_it != pNodeElement.Child.end(); el_it++) +} + +void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement &pNodeElement, aiMesh **pMesh) const { + // check argument + if (pMesh == nullptr) throw DeadlyImportError("Postprocess_BuildMesh. pMesh is nullptr."); + if (*pMesh != nullptr) throw DeadlyImportError("Postprocess_BuildMesh. *pMesh must be nullptr."); + + /************************************************************************************************************************************/ + /************************************************************ Geometry2D ************************************************************/ + /************************************************************************************************************************************/ + if ((pNodeElement.Type == CX3DImporter_NodeElement::ENET_Arc2D) || (pNodeElement.Type == CX3DImporter_NodeElement::ENET_ArcClose2D) || + (pNodeElement.Type == CX3DImporter_NodeElement::ENET_Circle2D) || (pNodeElement.Type == CX3DImporter_NodeElement::ENET_Disk2D) || + (pNodeElement.Type == CX3DImporter_NodeElement::ENET_Polyline2D) || (pNodeElement.Type == CX3DImporter_NodeElement::ENET_Polypoint2D) || + (pNodeElement.Type == CX3DImporter_NodeElement::ENET_Rectangle2D) || (pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleSet2D)) { + CX3DImporter_NodeElement_Geometry2D &tnemesh = *((CX3DImporter_NodeElement_Geometry2D *)&pNodeElement); // create alias for convenience + std::vector tarr; + + tarr.reserve(tnemesh.Vertices.size()); + for (std::list::iterator it = tnemesh.Vertices.begin(); it != tnemesh.Vertices.end(); ++it) + tarr.push_back(*it); + *pMesh = StandardShapes::MakeMesh(tarr, static_cast(tnemesh.NumIndices)); // create mesh from vertices using Assimp help. + + return; // mesh is build, nothing to do anymore. + } + /************************************************************************************************************************************/ + /************************************************************ Geometry3D ************************************************************/ + /************************************************************************************************************************************/ + // + // Predefined figures + // + if ((pNodeElement.Type == CX3DImporter_NodeElement::ENET_Box) || (pNodeElement.Type == CX3DImporter_NodeElement::ENET_Cone) || + (pNodeElement.Type == CX3DImporter_NodeElement::ENET_Cylinder) || (pNodeElement.Type == CX3DImporter_NodeElement::ENET_Sphere)) { + CX3DImporter_NodeElement_Geometry3D &tnemesh = *((CX3DImporter_NodeElement_Geometry3D *)&pNodeElement); // create alias for convenience + std::vector tarr; + + tarr.reserve(tnemesh.Vertices.size()); + for (std::list::iterator it = tnemesh.Vertices.begin(); it != tnemesh.Vertices.end(); ++it) + tarr.push_back(*it); + + *pMesh = StandardShapes::MakeMesh(tarr, static_cast(tnemesh.NumIndices)); // create mesh from vertices using Assimp help. + + return; // mesh is build, nothing to do anymore. + } + // + // Parametric figures + // + if (pNodeElement.Type == CX3DImporter_NodeElement::ENET_ElevationGrid) { + CX3DImporter_NodeElement_ElevationGrid &tnemesh = *((CX3DImporter_NodeElement_ElevationGrid *)&pNodeElement); // create alias for convenience + + // at first create mesh from existing vertices. + *pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIdx, tnemesh.Vertices); + // copy additional information from children + for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { + if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) + MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_Color *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_ColorRGBA) + MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_ColorRGBA *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Normal) + MeshGeometry_AddNormal(**pMesh, ((CX3DImporter_NodeElement_Normal *)*ch_it)->Value, tnemesh.NormalPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_TextureCoordinate) + MeshGeometry_AddTexCoord(**pMesh, ((CX3DImporter_NodeElement_TextureCoordinate *)*ch_it)->Value); + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of ElevationGrid: " + to_string((*ch_it)->Type) + "."); + } // for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_ElevationGrid) + // + // Indexed primitives sets + // + if (pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedFaceSet) { + CX3DImporter_NodeElement_IndexedSet &tnemesh = *((CX3DImporter_NodeElement_IndexedSet *)&pNodeElement); // create alias for convenience + + // at first search for node and create mesh. + for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { + if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { + *pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIndex, ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value); + } + } + + // copy additional information from children + for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { + if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) + MeshGeometry_AddColor(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((CX3DImporter_NodeElement_Color *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_ColorRGBA) + MeshGeometry_AddColor(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((CX3DImporter_NodeElement_ColorRGBA *)*ch_it)->Value, + tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { + } // skip because already read when mesh created. + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Normal) + MeshGeometry_AddNormal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((CX3DImporter_NodeElement_Normal *)*ch_it)->Value, + tnemesh.NormalPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_TextureCoordinate) + MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate *)*ch_it)->Value); + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedFaceSet: " + to_string((*ch_it)->Type) + "."); + } // for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedFaceSet) + + if (pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedLineSet) { + CX3DImporter_NodeElement_IndexedSet &tnemesh = *((CX3DImporter_NodeElement_IndexedSet *)&pNodeElement); // create alias for convenience + + // at first search for node and create mesh. + for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { + if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { + *pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIndex, ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value); + } + } + + // copy additional information from children + for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { + ai_assert(*pMesh); + if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) + MeshGeometry_AddColor(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((CX3DImporter_NodeElement_Color *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_ColorRGBA) + MeshGeometry_AddColor(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((CX3DImporter_NodeElement_ColorRGBA *)*ch_it)->Value, + tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { + } // skip because already read when mesh created. + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedLineSet: " + to_string((*ch_it)->Type) + "."); + } // for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedLineSet) + + if ((pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedTriangleSet) || + (pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedTriangleFanSet) || + (pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedTriangleStripSet)) { + CX3DImporter_NodeElement_IndexedSet &tnemesh = *((CX3DImporter_NodeElement_IndexedSet *)&pNodeElement); // create alias for convenience + + // at first search for node and create mesh. + for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { + if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { + *pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIndex, ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value); + } + } + + // copy additional information from children + for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { + ai_assert(*pMesh); + if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) + MeshGeometry_AddColor(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((CX3DImporter_NodeElement_Color *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_ColorRGBA) + MeshGeometry_AddColor(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((CX3DImporter_NodeElement_ColorRGBA *)*ch_it)->Value, + tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { + } // skip because already read when mesh created. + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Normal) + MeshGeometry_AddNormal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((CX3DImporter_NodeElement_Normal *)*ch_it)->Value, + tnemesh.NormalPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_TextureCoordinate) + MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate *)*ch_it)->Value); + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedTriangleSet or IndexedTriangleFanSet, or \ + IndexedTriangleStripSet: " + + to_string((*ch_it)->Type) + "."); + } // for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if((pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedTriangleFanSet) || (pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedTriangleStripSet)) + + if (pNodeElement.Type == CX3DImporter_NodeElement::ENET_Extrusion) { + CX3DImporter_NodeElement_IndexedSet &tnemesh = *((CX3DImporter_NodeElement_IndexedSet *)&pNodeElement); // create alias for convenience + + *pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIndex, tnemesh.Vertices); + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_Extrusion) + + // + // Primitives sets + // + if (pNodeElement.Type == CX3DImporter_NodeElement::ENET_PointSet) { + CX3DImporter_NodeElement_Set &tnemesh = *((CX3DImporter_NodeElement_Set *)&pNodeElement); // create alias for convenience + + // at first search for node and create mesh. + for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { + if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { + std::vector vec_copy; + + vec_copy.reserve(((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value.size()); + for (std::list::const_iterator it = ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value.begin(); + it != ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value.end(); ++it) { + vec_copy.push_back(*it); + } + + *pMesh = StandardShapes::MakeMesh(vec_copy, 1); + } + } + + // copy additional information from children + for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { + ai_assert(*pMesh); + if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) + MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_Color *)*ch_it)->Value, true); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_ColorRGBA) + MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_ColorRGBA *)*ch_it)->Value, true); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { + } // skip because already read when mesh created. + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of PointSet: " + to_string((*ch_it)->Type) + "."); + } // for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_PointSet) + + if (pNodeElement.Type == CX3DImporter_NodeElement::ENET_LineSet) { + CX3DImporter_NodeElement_Set &tnemesh = *((CX3DImporter_NodeElement_Set *)&pNodeElement); // create alias for convenience + + // at first search for node and create mesh. + for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { + if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { + *pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIndex, ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value); + } + } + + // copy additional information from children + for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { + ai_assert(*pMesh); + if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) + MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_Color *)*ch_it)->Value, true); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_ColorRGBA) + MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_ColorRGBA *)*ch_it)->Value, true); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { + } // skip because already read when mesh created. + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of LineSet: " + to_string((*ch_it)->Type) + "."); + } // for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_LineSet) + + if (pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleFanSet) { + CX3DImporter_NodeElement_Set &tnemesh = *((CX3DImporter_NodeElement_Set *)&pNodeElement); // create alias for convenience + + // at first search for node and create mesh. + for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { + if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { + *pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIndex, ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value); + } + } + + // copy additional information from children + for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { + if (nullptr == *pMesh) { + break; + } + if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) + MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_Color *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_ColorRGBA) + MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_ColorRGBA *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { + } // skip because already read when mesh created. + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Normal) + MeshGeometry_AddNormal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((CX3DImporter_NodeElement_Normal *)*ch_it)->Value, + tnemesh.NormalPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_TextureCoordinate) + MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate *)*ch_it)->Value); + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TrianlgeFanSet: " + to_string((*ch_it)->Type) + "."); + } // for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleFanSet) + + if (pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleSet) { + CX3DImporter_NodeElement_Set &tnemesh = *((CX3DImporter_NodeElement_Set *)&pNodeElement); // create alias for convenience + + // at first search for node and create mesh. + for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { + if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { + std::vector vec_copy; + + vec_copy.reserve(((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value.size()); + for (std::list::const_iterator it = ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value.begin(); + it != ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value.end(); ++it) { + vec_copy.push_back(*it); + } + + *pMesh = StandardShapes::MakeMesh(vec_copy, 3); + } + } + + // copy additional information from children + for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { + ai_assert(*pMesh); + if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) + MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_Color *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_ColorRGBA) + MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_ColorRGBA *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { + } // skip because already read when mesh created. + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Normal) + MeshGeometry_AddNormal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((CX3DImporter_NodeElement_Normal *)*ch_it)->Value, + tnemesh.NormalPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_TextureCoordinate) + MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate *)*ch_it)->Value); + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TrianlgeSet: " + to_string((*ch_it)->Type) + "."); + } // for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleSet) + + if (pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleStripSet) { + CX3DImporter_NodeElement_Set &tnemesh = *((CX3DImporter_NodeElement_Set *)&pNodeElement); // create alias for convenience + + // at first search for node and create mesh. + for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { + if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { + *pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIndex, ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value); + } + } + + // copy additional information from children + for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { + ai_assert(*pMesh); + if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) + MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_Color *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_ColorRGBA) + MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_ColorRGBA *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { + } // skip because already read when mesh created. + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Normal) + MeshGeometry_AddNormal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((CX3DImporter_NodeElement_Normal *)*ch_it)->Value, + tnemesh.NormalPerVertex); + else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_TextureCoordinate) + MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate *)*ch_it)->Value); + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TriangleStripSet: " + to_string((*ch_it)->Type) + "."); + } // for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleStripSet) + + throw DeadlyImportError("Postprocess_BuildMesh. Unknown mesh type: " + to_string(pNodeElement.Type) + "."); +} + +void X3DImporter::Postprocess_BuildNode(const X3DNodeElementBase &pNodeElement, aiNode &pSceneNode, std::list &pSceneMeshList, + std::list &pSceneMaterialList, std::list &pSceneLightList) const { + X3DElementList::const_iterator chit_begin = pNodeElement.Children.begin(); + X3DElementList::const_iterator chit_end = pNodeElement.Children.end(); + std::list SceneNode_Child; + std::list SceneNode_Mesh; + + // At first read all metadata + Postprocess_CollectMetadata(pNodeElement, pSceneNode); + // check if we have deal with grouping node. Which can contain transformation or switch + if (pNodeElement.Type == X3DElemType::ENET_Group) { + const CX3DNodeElementGroup &tne_group = *((CX3DNodeElementGroup*)&pNodeElement); // create alias for convenience + + pSceneNode.mTransformation = tne_group.Transformation; + if (tne_group.UseChoice) { + // If Choice is less than zero or greater than the number of nodes in the children field, nothing is chosen. + if ((tne_group.Choice < 0) || ((size_t)tne_group.Choice >= pNodeElement.Children.size())) { + chit_begin = pNodeElement.Children.end(); + chit_end = pNodeElement.Children.end(); + } else { + for (size_t i = 0; i < (size_t)tne_group.Choice; i++) + ++chit_begin; // forward iterator to chosen node. + + chit_end = chit_begin; + ++chit_end; // point end iterator to next element after chosen node. + } + } // if(tne_group.UseChoice) + } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_Group) + + // Reserve memory for fast access and check children. + for (std::list::const_iterator it = chit_begin; it != chit_end; ++it) { // in this loop we do not read metadata because it's already read at begin. + if ((*it)->Type == X3DElemType::ENET_Group) { + // if child is group then create new node and do recursive call. + aiNode *new_node = new aiNode; + + new_node->mName = (*it)->ID; + new_node->mParent = &pSceneNode; + SceneNode_Child.push_back(new_node); + Postprocess_BuildNode(**it, *new_node, pSceneMeshList, pSceneMaterialList, pSceneLightList); + } else if ((*it)->Type == X3DElemType::ENET_Shape) { + // shape can contain only one geometry and one appearance nodes. + Postprocess_BuildShape(*((CX3DImporter_NodeElement_Shape *)*it), SceneNode_Mesh, pSceneMeshList, pSceneMaterialList); + } else if (((*it)->Type == X3DElemType::ENET_DirectionalLight) || ((*it)->Type == X3DElemType::ENET_PointLight) || + ((*it)->Type == X3DElemType::ENET_SpotLight)) { + Postprocess_BuildLight(*((X3DElemType *)*it), pSceneLightList); + } else if (!PostprocessHelper_ElementIsMetadata((*it)->Type)) // skip metadata + { + throw DeadlyImportError("Postprocess_BuildNode. Unknown type: " + to_string((*it)->Type) + "."); + } + } // for(std::list::const_iterator it = chit_begin; it != chit_end; it++) + + // copy data about children and meshes to aiNode. + if (!SceneNode_Child.empty()) { + std::list::const_iterator it = SceneNode_Child.begin(); + + pSceneNode.mNumChildren = static_cast(SceneNode_Child.size()); + pSceneNode.mChildren = new aiNode *[pSceneNode.mNumChildren]; + for (size_t i = 0; i < pSceneNode.mNumChildren; i++) + pSceneNode.mChildren[i] = *it++; + } + + if (!SceneNode_Mesh.empty()) { + std::list::const_iterator it = SceneNode_Mesh.begin(); + + pSceneNode.mNumMeshes = static_cast(SceneNode_Mesh.size()); + pSceneNode.mMeshes = new unsigned int[pSceneNode.mNumMeshes]; + for (size_t i = 0; i < pSceneNode.mNumMeshes; i++) + pSceneNode.mMeshes[i] = *it++; + } + + // that's all. return to previous deals +} + +void X3DImporter::Postprocess_BuildShape(const CX3DImporter_NodeElement_Shape &pShapeNodeElement, std::list &pNodeMeshInd, + std::list &pSceneMeshList, std::list &pSceneMaterialList) const { + aiMaterial *tmat = nullptr; + aiMesh *tmesh = nullptr; + X3DElemType mesh_type = X3DElemType::ENET_Invalid; + unsigned int mat_ind = 0; + + for (X3DElementList::const_iterator it = pShapeNodeElement.Children.begin(); it != pShapeNodeElement.Children.end(); ++it) { + if (PostprocessHelper_ElementIsMesh((*it)->Type)) { + Postprocess_BuildMesh(**it, &tmesh); + if (tmesh != nullptr) { + // if mesh successfully built then add data about it to arrays + pNodeMeshInd.push_back(static_cast(pSceneMeshList.size())); + pSceneMeshList.push_back(tmesh); + // keep mesh type. Need above for texture coordinate generation. + mesh_type = (*it)->Type; + } + } else if ((*it)->Type == X3DElemType::ENET_Appearance) { + Postprocess_BuildMaterial(**it, &tmat); + if (tmat != nullptr) { + // if material successfully built then add data about it to array + mat_ind = static_cast(pSceneMaterialList.size()); + pSceneMaterialList.push_back(tmat); + } + } + } // for(std::list::const_iterator it = pShapeNodeElement.Child.begin(); it != pShapeNodeElement.Child.end(); it++) + + // associate read material with read mesh. + if ((tmesh != nullptr) && (tmat != nullptr)) { + tmesh->mMaterialIndex = mat_ind; + // Check texture mapping. If material has texture but mesh has no texture coordinate then try to ask Assimp to generate texture coordinates. + if ((tmat->GetTextureCount(aiTextureType_DIFFUSE) != 0) && !tmesh->HasTextureCoords(0)) { + int32_t tm; + aiVector3D tvec3; + + switch (mesh_type) { + case X3DElemType::ENET_Box: + tm = aiTextureMapping_BOX; + break; + case X3DElemType::ENET_Cone: + case X3DElemType::ENET_Cylinder: + tm = aiTextureMapping_CYLINDER; + break; + case X3DElemType::ENET_Sphere: + tm = aiTextureMapping_SPHERE; + break; + default: + tm = aiTextureMapping_PLANE; + break; + } // switch(mesh_type) + + tmat->AddProperty(&tm, 1, AI_MATKEY_MAPPING_DIFFUSE(0)); + } // if((tmat->GetTextureCount(aiTextureType_DIFFUSE) != 0) && !tmesh->HasTextureCoords(0)) + } // if((tmesh != nullptr) && (tmat != nullptr)) +} + +void X3DImporter::Postprocess_CollectMetadata(const CX3DImporter_NodeElement &pNodeElement, aiNode &pSceneNode) const { + X3DElementList meta_list; + size_t meta_idx; + + PostprocessHelper_CollectMetadata(pNodeElement, meta_list); // find metadata in current node element. + if (!meta_list.empty()) { + if (pSceneNode.mMetaData != nullptr) { + throw DeadlyImportError("Postprocess. MetaData member in node are not nullptr. Something went wrong."); + } + + // copy collected metadata to output node. + pSceneNode.mMetaData = aiMetadata::Alloc(static_cast(meta_list.size())); + meta_idx = 0; + for (X3DElementList::const_iterator it = meta_list.begin(); it != meta_list.end(); ++it, ++meta_idx) { + CX3DImporter_NodeElement_Meta *cur_meta = (CX3DImporter_NodeElement_Meta *)*it; + + // due to limitations we can add only first element of value list. + // Add an element according to its type. + if ((*it)->Type == CX3DImporter_NodeElement::ENET_MetaBoolean) { + if (((CX3DImporter_NodeElement_MetaBoolean *)cur_meta)->Value.size() > 0) + pSceneNode.mMetaData->Set(static_cast(meta_idx), cur_meta->Name, *(((CX3DImporter_NodeElement_MetaBoolean *)cur_meta)->Value.begin())); + } else if ((*it)->Type == CX3DImporter_NodeElement::ENET_MetaDouble) { + if (((CX3DImporter_NodeElement_MetaDouble *)cur_meta)->Value.size() > 0) + pSceneNode.mMetaData->Set(static_cast(meta_idx), cur_meta->Name, (float)*(((CX3DImporter_NodeElement_MetaDouble *)cur_meta)->Value.begin())); + } else if ((*it)->Type == CX3DImporter_NodeElement::ENET_MetaFloat) { + if (((CX3DImporter_NodeElement_MetaFloat *)cur_meta)->Value.size() > 0) + pSceneNode.mMetaData->Set(static_cast(meta_idx), cur_meta->Name, *(((CX3DImporter_NodeElement_MetaFloat *)cur_meta)->Value.begin())); + } else if ((*it)->Type == CX3DImporter_NodeElement::ENET_MetaInteger) { + if (((CX3DImporter_NodeElement_MetaInteger *)cur_meta)->Value.size() > 0) + pSceneNode.mMetaData->Set(static_cast(meta_idx), cur_meta->Name, *(((CX3DImporter_NodeElement_MetaInteger *)cur_meta)->Value.begin())); + } else if ((*it)->Type == CX3DImporter_NodeElement::ENET_MetaString) { + if (((CX3DImporter_NodeElement_MetaString *)cur_meta)->Value.size() > 0) { + aiString tstr(((CX3DImporter_NodeElement_MetaString *)cur_meta)->Value.begin()->data()); + + pSceneNode.mMetaData->Set(static_cast(meta_idx), cur_meta->Name, tstr); + } + } else { + throw DeadlyImportError("Postprocess. Unknown metadata type."); + } // if((*it)->Type == CX3DImporter_NodeElement::ENET_Meta*) else + } // for(std::list::const_iterator it = meta_list.begin(); it != meta_list.end(); it++) + } // if( !meta_list.empty() ) +} + #endif // !ASSIMP_BUILD_NO_X3D_IMPORTER + +} // namespace Assimp diff --git a/code/AssetLib/X3D/X3DImporter.hpp b/code/AssetLib/X3D/X3DImporter.hpp index f65dda559..559e4932f 100644 --- a/code/AssetLib/X3D/X3DImporter.hpp +++ b/code/AssetLib/X3D/X3DImporter.hpp @@ -41,7 +41,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef INCLUDED_AI_X3D_IMPORTER_H #define INCLUDED_AI_X3D_IMPORTER_H - #include #include #include @@ -50,8 +49,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include #include +#include #include namespace Assimp { @@ -288,8 +287,248 @@ protected: } }; +/// This struct hold value. +struct CX3DImporter_NodeElement_Color : X3DNodeElementBase { + std::list Value; ///< Stored value. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + CX3DImporter_NodeElement_Color(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_Color, pParent) {} + +}; // struct CX3DImporter_NodeElement_Color + +/// This struct hold value. +struct CX3DImporter_NodeElement_ColorRGBA : X3DNodeElementBase { + std::list Value; ///< Stored value. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + CX3DImporter_NodeElement_ColorRGBA(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_ColorRGBA, pParent) {} + +}; // struct CX3DImporter_NodeElement_ColorRGBA + +/// This struct hold value. +struct CX3DImporter_NodeElement_Coordinate : public X3DNodeElementBase { + std::list Value; ///< Stored value. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + CX3DImporter_NodeElement_Coordinate(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_Coordinate, pParent) {} + +}; // struct CX3DImporter_NodeElement_Coordinate + +/// This struct hold value. +struct CX3DImporter_NodeElement_Normal : X3DNodeElementBase { + std::list Value; ///< Stored value. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + CX3DImporter_NodeElement_Normal(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_Normal, pParent) {} + +}; // struct CX3DImporter_NodeElement_Normal + +/// This struct hold value. +struct CX3DImporter_NodeElement_TextureCoordinate : X3DNodeElementBase { + std::list Value; ///< Stored value. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + CX3DImporter_NodeElement_TextureCoordinate(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_TextureCoordinate, pParent) {} + +}; // struct CX3DImporter_NodeElement_TextureCoordinate + +/// Two-dimensional figure. +struct CX3DImporter_NodeElement_Geometry2D : X3DNodeElementBase { + std::list Vertices; ///< Vertices list. + size_t NumIndices; ///< Number of indices in one face. + bool Solid; ///< Flag: if true then render must use back-face culling, else render must draw both sides of object. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pType - type of geometry object. + CX3DImporter_NodeElement_Geometry2D(X3DElemType pType, X3DNodeElementBase *pParent) : + X3DNodeElementBase(pType, pParent), Solid(true) {} + +}; // class CX3DImporter_NodeElement_Geometry2D + +/// Three-dimensional body. +struct CX3DImporter_NodeElement_Geometry3D : X3DNodeElementBase { + std::list Vertices; ///< Vertices list. + size_t NumIndices; ///< Number of indices in one face. + bool Solid; ///< Flag: if true then render must use back-face culling, else render must draw both sides of object. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pType - type of geometry object. + CX3DImporter_NodeElement_Geometry3D(X3DElemType pType, X3DNodeElementBase *pParent) : + X3DNodeElementBase(pType, pParent), Vertices(), NumIndices(0), Solid(true) { + // empty + } +}; // class CX3DImporter_NodeElement_Geometry3D + +/// Uniform rectangular grid of varying height. +struct CX3DImporter_NodeElement_ElevationGrid : CX3DImporter_NodeElement_Geometry3D { + bool NormalPerVertex; ///< If true then normals are defined for every vertex, else for every face(line). + bool ColorPerVertex; ///< If true then colors are defined for every vertex, else for every face(line). + /// If the angle between the geometric normals of two adjacent faces is less than the crease angle, normals shall be calculated so that the faces are + /// shaded smoothly across the edge; otherwise, normals shall be calculated so that a lighting discontinuity across the edge is produced. + float CreaseAngle; + std::vector CoordIdx; ///< Coordinates list by faces. In X3D format: "-1" - delimiter for faces. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pType - type of geometry object. + CX3DImporter_NodeElement_ElevationGrid(X3DElemType pType, X3DNodeElementBase *pParent) : + CX3DImporter_NodeElement_Geometry3D(pType, pParent) {} +}; // class CX3DImporter_NodeElement_IndexedSet + +/// Shape with indexed vertices. +struct CX3DImporter_NodeElement_IndexedSet : public CX3DImporter_NodeElement_Geometry3D { + /// The ccw field defines the ordering of the vertex coordinates of the geometry with respect to user-given or automatically generated normal vectors + /// used in the lighting model equations. If ccw is TRUE, the normals shall follow the right hand rule; the orientation of each normal with respect to + /// the vertices (taken in order) shall be such that the vertices appear to be oriented in a counterclockwise order when the vertices are viewed (in the + /// local coordinate system of the Shape) from the opposite direction as the normal. If ccw is FALSE, the normals shall be oriented in the opposite + /// direction. If normals are not generated but are supplied using a Normal node, and the orientation of the normals does not match the setting of the + /// ccw field, results are undefined. + bool CCW; + std::vector ColorIndex; ///< Field to specify the polygonal faces by indexing into the or . + bool ColorPerVertex; ///< If true then colors are defined for every vertex, else for every face(line). + /// The convex field indicates whether all polygons in the shape are convex (TRUE). A polygon is convex if it is planar, does not intersect itself, + /// and all of the interior angles at its vertices are less than 180 degrees. Non planar and self intersecting polygons may produce undefined results + /// even if the convex field is FALSE. + bool Convex; + std::vector CoordIndex; ///< Field to specify the polygonal faces by indexing into the . + /// If the angle between the geometric normals of two adjacent faces is less than the crease angle, normals shall be calculated so that the faces are + /// shaded smoothly across the edge; otherwise, normals shall be calculated so that a lighting discontinuity across the edge is produced. + float CreaseAngle; + std::vector NormalIndex; ///< Field to specify the polygonal faces by indexing into the . + bool NormalPerVertex; ///< If true then normals are defined for every vertex, else for every face(line). + std::vector TexCoordIndex; ///< Field to specify the polygonal faces by indexing into the . + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pType - type of geometry object. + CX3DImporter_NodeElement_IndexedSet(X3DElemType pType, X3DNodeElementBase *pParent) : + CX3DImporter_NodeElement_Geometry3D(pType, pParent) {} +}; // class CX3DImporter_NodeElement_IndexedSet + +/// Shape with set of vertices. +struct CX3DImporter_NodeElement_Set : CX3DImporter_NodeElement_Geometry3D { + /// The ccw field defines the ordering of the vertex coordinates of the geometry with respect to user-given or automatically generated normal vectors + /// used in the lighting model equations. If ccw is TRUE, the normals shall follow the right hand rule; the orientation of each normal with respect to + /// the vertices (taken in order) shall be such that the vertices appear to be oriented in a counterclockwise order when the vertices are viewed (in the + /// local coordinate system of the Shape) from the opposite direction as the normal. If ccw is FALSE, the normals shall be oriented in the opposite + /// direction. If normals are not generated but are supplied using a Normal node, and the orientation of the normals does not match the setting of the + /// ccw field, results are undefined. + bool CCW; + bool ColorPerVertex; ///< If true then colors are defined for every vertex, else for every face(line). + bool NormalPerVertex; ///< If true then normals are defined for every vertex, else for every face(line). + std::vector CoordIndex; ///< Field to specify the polygonal faces by indexing into the . + std::vector NormalIndex; ///< Field to specify the polygonal faces by indexing into the . + std::vector TexCoordIndex; ///< Field to specify the polygonal faces by indexing into the . + std::vector VertexCount; ///< Field describes how many vertices are to be used in each polyline(polygon) from the field. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pType - type of geometry object. + CX3DImporter_NodeElement_Set(X3DElemType type, X3DNodeElementBase *pParent) : + CX3DImporter_NodeElement_Geometry3D(type, pParent) {} + +}; // class CX3DImporter_NodeElement_Set + +/// This struct hold value. +struct CX3DImporter_NodeElement_Shape : X3DNodeElementBase { + + /// Constructor + /// \param [in] pParent - pointer to parent node. + CX3DImporter_NodeElement_Shape(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_Shape, pParent) {} +}; // struct CX3DImporter_NodeElement_Shape + +/// This struct hold value. +struct CX3DImporter_NodeElement_Appearance : public X3DNodeElementBase { + + /// Constructor + /// \param [in] pParent - pointer to parent node. + CX3DImporter_NodeElement_Appearance(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_Appearance, pParent) {} + +}; // struct CX3DImporter_NodeElement_Appearance + +struct CX3DImporter_NodeElement_Material : public X3DNodeElementBase { + float AmbientIntensity; ///< Specifies how much ambient light from light sources this surface shall reflect. + aiColor3D DiffuseColor; ///< Reflects all X3D light sources depending on the angle of the surface with respect to the light source. + aiColor3D EmissiveColor; ///< Models "glowing" objects. This can be useful for displaying pre-lit models. + float Shininess; ///< Lower shininess values produce soft glows, while higher values result in sharper, smaller highlights. + aiColor3D SpecularColor; ///< The specularColor and shininess fields determine the specular highlights. + float Transparency; ///< Specifies how "clear" an object is, with 1.0 being completely transparent, and 0.0 completely opaque. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pType - type of geometry object. + CX3DImporter_NodeElement_Material(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_Material, pParent), + AmbientIntensity(0.0f), + DiffuseColor(), + EmissiveColor(), + Shininess(0.0f), + SpecularColor(), + Transparency(1.0f) { + // empty + } +}; // class CX3DImporter_NodeElement_Material + +/// This struct hold value. +struct CX3DImporter_NodeElement_ImageTexture : X3DNodeElementBase { + /// RepeatS and RepeatT, that specify how the texture wraps in the S and T directions. If repeatS is TRUE (the default), the texture map is repeated + /// outside the [0.0, 1.0] texture coordinate range in the S direction so that it fills the shape. If repeatS is FALSE, the texture coordinates are + /// clamped in the S direction to lie within the [0.0, 1.0] range. The repeatT field is analogous to the repeatS field. + bool RepeatS; + bool RepeatT; ///< See \ref RepeatS. + std::string URL; ///< URL of the texture. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + CX3DImporter_NodeElement_ImageTexture(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_ImageTexture, pParent) {} + +}; // struct CX3DImporter_NodeElement_ImageTexture + +/// This struct hold value. +struct CX3DImporter_NodeElement_TextureTransform : X3DNodeElementBase { + aiVector2D Center; ///< Specifies a translation offset in texture coordinate space about which the rotation and scale fields are applied. + float Rotation; ///< Specifies a rotation in angle base units of the texture coordinates about the center point after the scale has been applied. + aiVector2D Scale; ///< Specifies a scaling factor in S and T of the texture coordinates about the center point. + aiVector2D Translation; ///< Specifies a translation of the texture coordinates. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + CX3DImporter_NodeElement_TextureTransform(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_TextureTransform, pParent) {} + +}; // struct CX3DImporter_NodeElement_TextureTransform + struct CX3DNodeElementGroup : X3DNodeElementBase { aiMatrix4x4 Transformation; ///< Transformation matrix. + + /// As you know node elements can use already defined node elements when attribute "USE" is defined. + /// Standard search when looking for an element in the whole scene graph, existing at this moment. + /// If a node is marked as static, the children(or lower) can not search for elements in the nodes upper then static. + bool Static; + + bool UseChoice; ///< Flag: if true then use number from \ref Choice to choose what the child will be kept. + int32_t Choice; ///< Number of the child which will be kept. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pStatic - static node flag. + CX3DNodeElementGroup(X3DNodeElementBase *pParent, const bool pStatic = false) : + X3DNodeElementBase(X3DElemType::ENET_Group, pParent), Static(pStatic), UseChoice(false) {} }; struct X3DNodeElementMeta : X3DNodeElementBase { @@ -352,7 +591,7 @@ struct X3DNodeElementMetaSet : public X3DNodeElementMeta { } }; -struct X3DNodeElementMetaString : public X3DNodeElementMeta { +struct X3DNodeElementMetaString : X3DNodeElementMeta { std::list Value; ///< Stored value. explicit X3DNodeElementMetaString(X3DNodeElementBase *pParent) : @@ -361,6 +600,36 @@ struct X3DNodeElementMetaString : public X3DNodeElementMeta { } }; +/// \struct CX3DImporter_NodeElement_Light +/// This struct hold value. +struct X3DNodeNodeElementLight : X3DNodeElementBase { + float AmbientIntensity; ///< Specifies the intensity of the ambient emission from the light. + aiColor3D Color; ///< specifies the spectral colour properties of both the direct and ambient light emission as an RGB value. + aiVector3D Direction; ///< Specifies the direction vector of the illumination emanating from the light source in the local coordinate system. + /// \var Global + /// Field that determines whether the light is global or scoped. Global lights illuminate all objects that fall within their volume of lighting influence. + /// Scoped lights only illuminate objects that are in the same transformation hierarchy as the light. + bool Global; + float Intensity; ///< Specifies the brightness of the direct emission from the light. + /// \var Attenuation + /// PointLight node's illumination falls off with distance as specified by three attenuation coefficients. The attenuation factor + /// is: "1 / max(attenuation[0] + attenuation[1] * r + attenuation[2] * r2, 1)", where r is the distance from the light to the surface being illuminated. + aiVector3D Attenuation; + aiVector3D Location; ///< Specifies a translation offset of the centre point of the light source from the light's local coordinate system origin. + float Radius; ///< Specifies the radial extent of the solid angle and the maximum distance from location that may be illuminated by the light source. + float BeamWidth; ///< Specifies an inner solid angle in which the light source emits light at uniform full intensity. + float CutOffAngle; ///< The light source's emission intensity drops off from the inner solid angle (beamWidth) to the outer solid angle (cutOffAngle). + + /// Constructor + /// \param [in] pParent - pointer to parent node. + /// \param [in] pLightType - type of the light source. + X3DNodeNodeElementLight(X3DElemType pLightType, X3DNodeElementBase *pParent) : + X3DNodeElementBase(pLightType, pParent) {} + +}; // struct CX3DImporter_NodeElement_Light + +using X3DElementList = std::list; + class X3DImporter : public BaseImporter { public: std::list NodeElement_List; ///< All elements of scene graph. @@ -389,6 +658,9 @@ public: void readScene(XmlNode &node); void readViewpoint(XmlNode &node); void readMetadataObject(XmlNode &node); + void ParseDirectionalLight(XmlNode &node); + void Postprocess_BuildNode(const X3DNodeElementBase &pNodeElement, aiNode &pSceneNode, std::list &pSceneMeshList, + std::list &pSceneMaterialList, std::list &pSceneLightList) const; private: static const aiImporterDesc Description; From 3001d88172dfccbe917afd0815381d5524b3e854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Martin?= Date: Tue, 7 Sep 2021 15:04:08 +0200 Subject: [PATCH 06/28] Merge branch 'master' into x3d_pugi_migration --- .gitignore | 2 +- CMakeLists.txt | 20 +- cmake-modules/FindIrrXML.cmake | 17 - {cmake => cmake-modules}/HunterGate.cmake | 0 cmake-modules/assimp-hunter-config.cmake.in | 19 + .../assimp-plain-config.cmake.in | 0 cmake/assimp-hunter-config.cmake.in | 18 - code/AssetLib/3DS/3DSConverter.cpp | 11 +- code/AssetLib/3DS/3DSExporter.cpp | 2 +- code/AssetLib/3DS/3DSHelper.h | 76 +- code/AssetLib/3DS/3DSLoader.h | 9 + code/AssetLib/3MF/3MFTypes.h | 165 + code/AssetLib/3MF/3MFXmlTags.h | 12 +- code/AssetLib/3MF/D3MFImporter.cpp | 522 +- code/AssetLib/3MF/D3MFImporter.h | 34 +- code/AssetLib/3MF/D3MFOpcPackage.cpp | 69 +- code/AssetLib/3MF/D3MFOpcPackage.h | 14 +- code/AssetLib/3MF/XmlSerializer.cpp | 594 ++ code/AssetLib/3MF/XmlSerializer.h | 96 + code/AssetLib/AMF/AMFImporter.cpp | 2 +- code/AssetLib/AMF/AMFImporter_Geometry.cpp | 6 +- code/AssetLib/AMF/AMFImporter_Postprocess.cpp | 6 +- code/AssetLib/ASE/ASEParser.h | 8 +- code/AssetLib/Assbin/AssbinFileWriter.cpp | 2 +- code/AssetLib/Assjson/json_exporter.cpp | 42 +- code/AssetLib/Assjson/mesh_splitter.cpp | 8 +- code/AssetLib/Assjson/mesh_splitter.h | 6 +- code/AssetLib/Assxml/AssxmlExporter.cpp | 2 +- code/AssetLib/B3D/B3DImporter.cpp | 2 +- code/AssetLib/B3D/B3DImporter.h | 2 +- code/AssetLib/Blender/BlenderLoader.cpp | 2 +- code/AssetLib/Blender/BlenderModifier.h | 18 +- code/AssetLib/C4D/C4DImporter.cpp | 6 + code/AssetLib/COB/COBLoader.cpp | 4 +- code/AssetLib/COB/COBLoader.h | 2 +- code/AssetLib/Collada/ColladaLoader.cpp | 25 +- code/AssetLib/Collada/ColladaParser.cpp | 241 +- code/AssetLib/DXF/DXFLoader.cpp | 4 +- code/AssetLib/DXF/DXFLoader.h | 2 +- code/AssetLib/FBX/FBXConverter.cpp | 17 +- code/AssetLib/FBX/FBXConverter.h | 4 +- code/AssetLib/FBX/FBXDocument.cpp | 11 +- code/AssetLib/FBX/FBXDocument.h | 5 + code/AssetLib/FBX/FBXExportNode.cpp | 5 +- code/AssetLib/FBX/FBXExportNode.h | 7 +- code/AssetLib/FBX/FBXExporter.cpp | 145 +- code/AssetLib/FBX/FBXExporter.h | 21 +- code/AssetLib/FBX/FBXMaterial.cpp | 13 +- code/AssetLib/FBX/FBXMeshGeometry.cpp | 2 +- code/AssetLib/FBX/FBXMeshGeometry.h | 8 +- code/AssetLib/FBX/FBXParser.cpp | 28 +- code/AssetLib/FBX/FBXProperties.cpp | 9 +- code/AssetLib/FBX/FBXProperties.h | 6 +- code/AssetLib/FBX/FBXUtil.cpp | 2 +- code/AssetLib/HMP/HMPLoader.cpp | 4 +- code/AssetLib/IFC/IFCBoolean.cpp | 2 +- code/AssetLib/IFC/IFCCurve.cpp | 2 +- code/AssetLib/IFC/IFCGeometry.cpp | 2 +- code/AssetLib/IFC/IFCMaterial.cpp | 6 +- code/AssetLib/IFC/IFCOpenings.cpp | 8 +- code/AssetLib/IFC/IFCReaderGen1_2x3.cpp | 232 +- code/AssetLib/IFC/IFCReaderGen2_2x3.cpp | 162 +- code/AssetLib/IFC/IFCReaderGen_4.cpp | 392 +- code/AssetLib/IFC/IFCReaderGen_4.h | 26 +- code/AssetLib/IFC/IFCUtil.h | 14 +- code/AssetLib/Irr/IRRLoader.h | 2 +- code/AssetLib/LWO/LWOAnimation.h | 2 +- code/AssetLib/LWS/LWSLoader.cpp | 6 +- code/AssetLib/M3D/M3DImporter.cpp | 16 +- code/AssetLib/M3D/M3DImporter.h | 4 +- code/AssetLib/M3D/M3DWrapper.h | 68 +- code/AssetLib/M3D/m3d.h | 1230 +-- code/AssetLib/MD5/MD5Loader.cpp | 2 +- code/AssetLib/MDC/MDCFileData.h | 4 +- code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp | 4 +- code/AssetLib/MMD/MMDPmxParser.cpp | 8 +- code/AssetLib/OFF/OFFLoader.cpp | 10 +- code/AssetLib/Obj/ObjExporter.h | 18 +- code/AssetLib/Obj/ObjFileImporter.cpp | 4 +- code/AssetLib/Obj/ObjFileMtlImporter.cpp | 2 +- code/AssetLib/Ogre/OgreMaterial.cpp | 4 +- code/AssetLib/Ply/PlyParser.cpp | 8 +- code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp | 4 +- code/AssetLib/SMD/SMDLoader.cpp | 2 +- code/AssetLib/STEPParser/STEPFileReader.cpp | 9 +- code/AssetLib/STL/STLExporter.cpp | 8 +- code/AssetLib/STL/STLLoader.cpp | 4 +- code/AssetLib/Step/STEPFile.h | 4 +- code/AssetLib/Step/StepExporter.cpp | 120 +- code/AssetLib/X/XFileExporter.cpp | 6 +- code/AssetLib/X/XFileExporter.h | 6 +- code/AssetLib/X/XFileImporter.cpp | 4 +- code/AssetLib/X3D/X3DExporter.hpp | 6 +- code/AssetLib/X3D/X3DImporter.cpp | 1 - code/AssetLib/XGL/XGLLoader.cpp | 9 +- code/AssetLib/XGL/XGLLoader.h | 2 +- code/AssetLib/glTF/glTFAsset.h | 10 +- code/AssetLib/glTF/glTFAsset.inl | 2 +- code/AssetLib/glTF/glTFCommon.h | 6 +- code/AssetLib/glTF/glTFExporter.cpp | 4 +- code/AssetLib/glTF2/glTF2Asset.h | 103 +- code/AssetLib/glTF2/glTF2Asset.inl | 184 +- code/AssetLib/glTF2/glTF2AssetWriter.inl | 16 +- code/AssetLib/glTF2/glTF2Exporter.cpp | 314 +- code/AssetLib/glTF2/glTF2Exporter.h | 26 +- code/AssetLib/glTF2/glTF2Importer.cpp | 120 +- code/CMakeLists.txt | 28 +- code/Common/Assimp.cpp | 46 + code/Common/DefaultIOStream.cpp | 6 +- code/Common/DefaultIOSystem.cpp | 8 +- code/Common/Exporter.cpp | 4 +- code/Common/FileSystemFilter.h | 4 +- code/Common/Importer.cpp | 80 +- code/Common/Importer.h | 6 +- code/Common/ImporterRegistry.cpp | 4 +- code/Common/RemoveComments.cpp | 7 +- code/Common/SceneCombiner.cpp | 18 +- code/Common/ScenePreprocessor.cpp | 3 + code/Common/Win32DebugLogStream.h | 8 +- code/Common/material.cpp | 12 +- code/Material/MaterialSystem.cpp | 12 +- code/Pbrt/PbrtExporter.cpp | 26 +- code/Pbrt/PbrtExporter.h | 4 +- code/PostProcessing/ArmaturePopulate.h | 2 +- .../PostProcessing/DropFaceNormalsProcess.cpp | 2 +- code/PostProcessing/EmbedTexturesProcess.cpp | 40 +- code/PostProcessing/EmbedTexturesProcess.h | 5 +- code/PostProcessing/FindDegenerates.cpp | 2 +- code/PostProcessing/FindInstancesProcess.cpp | 2 +- code/PostProcessing/MakeVerboseFormat.h | 2 +- code/PostProcessing/OptimizeGraph.cpp | 2 +- code/PostProcessing/PretransformVertices.cpp | 2 +- .../RemoveRedundantMaterials.cpp | 2 +- code/PostProcessing/ScaleProcess.cpp | 38 +- code/PostProcessing/ScaleProcess.h | 4 +- code/PostProcessing/SortByPTypeProcess.cpp | 2 +- .../SplitByBoneCountProcess.cpp | 12 +- code/PostProcessing/SplitLargeMeshes.cpp | 4 +- code/PostProcessing/TextureTransform.cpp | 2 +- code/PostProcessing/TriangulateProcess.cpp | 12 +- contrib/draco/.ruby-version | 1 - contrib/draco/.travis.yml | 31 - contrib/draco/CMakeLists.txt | 8 +- contrib/draco/README.md | 6 +- .../draco/cmake/draco_build_definitions.cmake | 9 +- contrib/draco/cmake/draco_features.cmake | 63 - contrib/draco/cmake/draco_flags.cmake | 9 + contrib/draco/cmake/draco_install.cmake | 2 +- contrib/draco/cmake/draco_sanitizer.cmake | 20 +- contrib/draco/cmake/draco_targets.cmake | 24 +- contrib/draco/src/draco/core/cycle_timer.cc | 14 +- contrib/draco/src/draco/core/cycle_timer.h | 7 +- contrib/draco/src/draco/io/parser_utils.cc | 3 +- contrib/draco/src/draco/io/ply_reader.cc | 4 +- .../draco/src/draco/io/stdio_file_reader.cc | 7 + contrib/openddlparser/code/OpenDDLExport.cpp | 3 +- contrib/openddlparser/code/OpenDDLParser.cpp | 30 +- contrib/openddlparser/code/Value.cpp | 9 +- .../include/openddlparser/OpenDDLParser.h | 13 +- contrib/poly2tri/poly2tri/sweep/sweep.cc | 2 +- {code/Pbrt => contrib/stb}/stb_image.h | 0 contrib/stb_image/stb_image.h | 7462 ----------------- doc/Doxyfile.in | 41 +- doc/dox.h | 18 +- doc/dox_cmd.h | 38 +- fuzz/assimp_fuzzer.cc | 2 +- include/assimp/BaseImporter.h | 2 +- include/assimp/Compiler/poppack1.h | 4 +- include/assimp/Compiler/pushpack1.h | 4 +- include/assimp/Exceptional.h | 2 +- include/assimp/Exporter.hpp | 2 +- include/assimp/IOStreamBuffer.h | 6 +- include/assimp/IOSystem.hpp | 8 +- include/assimp/Logger.hpp | 10 +- include/assimp/MemoryIOWrapper.h | 6 +- include/assimp/SmallVector.h | 8 +- include/assimp/SmoothingGroups.inl | 22 +- include/assimp/XmlParser.h | 10 +- include/assimp/ai_assert.h | 2 +- include/assimp/anim.h | 6 +- include/assimp/cimport.h | 12 +- include/assimp/defs.h | 2 +- include/assimp/fast_atof.h | 4 +- include/assimp/light.h | 2 +- include/assimp/material.h | 130 +- include/assimp/matrix4x4.h | 2 +- include/assimp/matrix4x4.inl | 2 +- include/assimp/mesh.h | 10 +- include/assimp/metadata.h | 2 +- include/assimp/pbrmaterial.h | 46 +- include/assimp/postprocess.h | 14 +- include/assimp/quaternion.h | 2 +- include/assimp/scene.h | 49 +- include/assimp/vector2.inl | 14 +- include/assimp/vector3.h | 33 +- .../windows-innosetup/readme_installer.txt | 2 +- .../readme_installer_vieweronly.txt | 2 +- packaging/windows-mkzip/bin_readme.txt | 2 +- port/AndroidJNI/CMakeLists.txt | 4 +- port/AssimpDelphi/Readme.txt | 2 +- port/PyAssimp/pyassimp/helper.py | 3 +- port/jassimp/jassimp-native/src/jassimp.cpp | 164 +- .../ModelLoaderHelperClasses.h | 26 +- samples/SimpleAssimpViewX/MyDocument.h | 18 +- samples/SimpleOpenGL/Sample_SimpleOpenGL.c | 4 +- .../SimpleTexturedDirectx11/CMakeLists.txt | 6 +- .../SimpleTexturedDirectx11/ModelLoader.cpp | 2 +- .../SimpleTexturedDirectx11/TextureLoader.cpp | 52 +- .../SimpleTexturedDirectx11/main.cpp | 10 +- .../src/model_loading.cpp | 6 +- test/models-nonbsd/3D/mar_rifle.source.txt | 6 +- test/models-nonbsd/3DS/cart_wheel.source.txt | 6 +- test/models-nonbsd/3DS/mar_rifle.source.txt | 6 +- test/models-nonbsd/3DS/mp5_sil.source.txt | 6 +- test/models-nonbsd/ASE/Rifle.source.txt | 6 +- test/models-nonbsd/ASE/Rifle2.source.txt | 6 +- .../BLEND/fleurOptonl.source.txt | 16 +- test/models-nonbsd/DXF/rifle.source.txt | 6 +- .../FBX/2013_ASCII/cart_wheel.source.txt | 6 +- .../kwxport_test_vcolors.fbx.source.txt | 6 +- .../FBX/2013_ASCII/mar_rifle.source.txt | 6 +- .../FBX/2013_ASCII/mp5_sil.source.txt | 6 +- .../FBX/2013_BINARY/cart_wheel.source.txt | 6 +- .../kwxport_test_vcolors.fbx.source.txt | 6 +- .../FBX/2013_BINARY/mar_rifle.source.txt | 6 +- .../FBX/2013_BINARY/mp5_sil.source.txt | 6 +- .../LWO2/LWSReferences/QuickDraw.source.txt | 10 +- test/models-nonbsd/LWO/LWO2/rifle.source.txt | 6 +- test/models-nonbsd/MD2/source.txt | 6 +- test/models-nonbsd/MD5/BoarMan.source.txt | 4 +- .../MDL/IDPO (Quake1)/gijoe-readme.txt | 14 +- test/models-nonbsd/MDL/IDPO (Quake1)/steg.txt | 8 +- .../MDL/IDPO (Quake1)/tekmechbot.txt | 6 +- test/models-nonbsd/NFF/NFFSense8/credits.txt | 2 +- test/models-nonbsd/OBJ/rifle.source.txt | 6 +- test/models/3DS/UVTransformTest/note.txt | 4 +- test/models/ASE/MotionCaptureROM.source.txt | 2 +- test/models/Collada/human.zae | Bin 0 -> 1093924 bytes .../kwxport_test_vcolors.dae.source.txt | 6 +- .../IRR/warn_dwarf_scaling_is_intended.txt | 2 +- test/models/MD2/faerie-source.txt | 2 +- test/models/MD2/sidney-source.txt | 2 +- test/models/Q3D/E-AT-AT.source.txt | 2 +- test/models/Q3D/earth.source.txt | 2 +- test/models/WRL/credits.txt | 2 +- test/models/X/anim_test.txt | 2 +- .../X/kwxport_test_cubewithvcolors.source.txt | 6 +- test/models/X/test.txt | 2 +- .../glTF2/ClearCoat-glTF/ClearCoatLabels.png | Bin 0 -> 10270 bytes .../glTF2/ClearCoat-glTF/ClearCoatTest.bin | Bin 0 -> 50328 bytes .../glTF2/ClearCoat-glTF/ClearCoatTest.gltf | 1669 ++++ .../glTF2/ClearCoat-glTF/PartialCoating.png | Bin 0 -> 5077 bytes .../ClearCoat-glTF/PartialCoating_Alpha.png | Bin 0 -> 5065 bytes .../ClearCoat-glTF/PlasticWrap_normals.jpg | Bin 0 -> 144210 bytes .../glTF2/ClearCoat-glTF/RibsNormal.png | Bin 0 -> 1605 bytes .../glTF2/ClearCoat-glTF/RoughnessStripes.png | Bin 0 -> 5033 bytes test/models/invalid/readme.txt | 8 +- test/regression/README.txt | 6 +- test/unit/AbstractImportExportBase.h | 2 +- test/unit/Common/utStandardShapes.cpp | 2 +- .../MDL/utMDLImporter_HL1_Nodes.cpp | 4 +- .../ImportExport/utAssjsonImportExport.cpp | 13 +- test/unit/RandomNumberGeneration.h | 4 +- test/unit/SceneDiffer.cpp | 2 +- test/unit/SceneDiffer.h | 2 +- test/unit/TestIOSystem.h | 2 +- test/unit/utColladaImportExport.cpp | 22 + test/unit/utDefaultIOStream.cpp | 2 +- test/unit/utFBXImporterExporter.cpp | 4 +- test/unit/utFindDegenerates.cpp | 4 +- test/unit/utIOStreamBuffer.cpp | 6 +- test/unit/utIOSystem.cpp | 10 +- test/unit/utIssues.cpp | 2 +- test/unit/utTypes.cpp | 4 +- test/unit/utVersion.cpp | 2 +- test/unit/utglTF2ImportExport.cpp | 186 +- tools/assimp_cmd/CMakeLists.txt | 2 +- tools/assimp_cmd/Export.cpp | 18 +- tools/assimp_cmd/ImageExtractor.cpp | 20 +- tools/assimp_cmd/Info.cpp | 2 +- tools/assimp_cmd/Main.cpp | 62 +- tools/assimp_cmd/Main.h | 40 +- tools/assimp_cmd/WriteDump.cpp | 18 +- tools/assimp_cmd/resource.h | 2 +- tools/assimp_view/AnimEvaluator.cpp | 5 +- tools/assimp_view/AnimEvaluator.h | 26 +- tools/assimp_view/CMakeLists.txt | 2 +- tools/assimp_view/Display.cpp | 6 +- tools/assimp_view/Material.cpp | 7 +- tools/assimp_view/MaterialManager.h | 73 +- tools/assimp_view/MeshRenderer.cpp | 9 +- tools/assimp_view/MessageProc.cpp | 2 +- tools/assimp_view/Shaders.cpp | 18 +- tools/assimp_view/assimp_view.cpp | 25 +- tools/assimp_view/resource.h | 2 +- 295 files changed, 5518 insertions(+), 11500 deletions(-) delete mode 100644 cmake-modules/FindIrrXML.cmake rename {cmake => cmake-modules}/HunterGate.cmake (100%) create mode 100644 cmake-modules/assimp-hunter-config.cmake.in rename {cmake => cmake-modules}/assimp-plain-config.cmake.in (100%) delete mode 100644 cmake/assimp-hunter-config.cmake.in create mode 100644 code/AssetLib/3MF/3MFTypes.h create mode 100644 code/AssetLib/3MF/XmlSerializer.cpp create mode 100644 code/AssetLib/3MF/XmlSerializer.h delete mode 100644 contrib/draco/.ruby-version delete mode 100644 contrib/draco/.travis.yml delete mode 100644 contrib/draco/cmake/draco_features.cmake rename {code/Pbrt => contrib/stb}/stb_image.h (100%) delete mode 100644 contrib/stb_image/stb_image.h create mode 100644 test/models/Collada/human.zae create mode 100644 test/models/glTF2/ClearCoat-glTF/ClearCoatLabels.png create mode 100644 test/models/glTF2/ClearCoat-glTF/ClearCoatTest.bin create mode 100644 test/models/glTF2/ClearCoat-glTF/ClearCoatTest.gltf create mode 100644 test/models/glTF2/ClearCoat-glTF/PartialCoating.png create mode 100644 test/models/glTF2/ClearCoat-glTF/PartialCoating_Alpha.png create mode 100644 test/models/glTF2/ClearCoat-glTF/PlasticWrap_normals.jpg create mode 100644 test/models/glTF2/ClearCoat-glTF/RibsNormal.png create mode 100644 test/models/glTF2/ClearCoat-glTF/RoughnessStripes.png diff --git a/.gitignore b/.gitignore index 0a999d3aa..7849cab65 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,7 @@ CMakeSettings.json # Output bin/ lib/ - +x64/ # QtCreator CMakeLists.txt.user diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b9d6fc55..bfa30e96a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,10 +44,10 @@ CMAKE_MINIMUM_REQUIRED( VERSION 3.10 ) option(ASSIMP_HUNTER_ENABLED "Enable Hunter package manager support" OFF) IF(ASSIMP_HUNTER_ENABLED) - include("cmake/HunterGate.cmake") + include("cmake-modules/HunterGate.cmake") HunterGate( - URL "https://github.com/cpp-pm/hunter/archive/v0.23.293.tar.gz" - SHA1 "e8e5470652db77149d9b38656db2a6c0b7642693" + URL "https://github.com/cpp-pm/hunter/archive/v0.23.311.tar.gz" + SHA1 "1a82b9b73055879181cb1466b2ab5d48ee8ae410" ) add_definitions(-DASSIMP_USE_HUNTER) @@ -135,11 +135,11 @@ IF ( WIN32 ) # Use subset of Windows.h ADD_DEFINITIONS( -DWIN32_LEAN_AND_MEAN ) - OPTION ( ASSIMP_BUILD_ASSIMP_VIEW - "If the Assimp view tool is built. (requires DirectX)" - OFF ) - IF(MSVC) + OPTION ( ASSIMP_BUILD_ASSIMP_VIEW + "If the Assimp view tool is built. (requires DirectX)" + OFF ) + OPTION( ASSIMP_INSTALL_PDB "Install MSVC debug files." ON ) @@ -268,6 +268,8 @@ ELSEIF(MSVC) ADD_COMPILE_OPTIONS(/wd4351) ENDIF() SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_DEBUG /Zi /Od") + SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi") + SET(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG:FULL /PDBALTPATH:%_PDB% /OPT:REF /OPT:ICF") ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "Clang" ) IF(NOT ASSIMP_HUNTER_ENABLED) SET(CMAKE_CXX_STANDARD 11) @@ -395,14 +397,14 @@ set(GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated") IF(ASSIMP_HUNTER_ENABLED) set(CONFIG_INSTALL_DIR "lib/cmake/${PROJECT_NAME}") - set(CMAKE_CONFIG_TEMPLATE_FILE "cmake/assimp-hunter-config.cmake.in") + set(CMAKE_CONFIG_TEMPLATE_FILE "cmake-modules/assimp-hunter-config.cmake.in") set(NAMESPACE "${PROJECT_NAME}::") set(TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets") set(VERSION_CONFIG "${GENERATED_DIR}/${PROJECT_NAME}ConfigVersion.cmake") set(PROJECT_CONFIG "${GENERATED_DIR}/${PROJECT_NAME}Config.cmake") ELSE() set(CONFIG_INSTALL_DIR "${ASSIMP_LIB_INSTALL_DIR}/cmake/assimp-${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}") - set(CMAKE_CONFIG_TEMPLATE_FILE "cmake/assimp-plain-config.cmake.in") + set(CMAKE_CONFIG_TEMPLATE_FILE "cmake-modules/assimp-plain-config.cmake.in") string(TOLOWER ${PROJECT_NAME} PROJECT_NAME_LOWERCASE) set(NAMESPACE "${PROJECT_NAME_LOWERCASE}::") set(TARGETS_EXPORT_NAME "${PROJECT_NAME_LOWERCASE}Targets") diff --git a/cmake-modules/FindIrrXML.cmake b/cmake-modules/FindIrrXML.cmake deleted file mode 100644 index 5434e0b86..000000000 --- a/cmake-modules/FindIrrXML.cmake +++ /dev/null @@ -1,17 +0,0 @@ -# Find IrrXMl from irrlicht project -# -# Find LibIrrXML headers and library -# -# IRRXML_FOUND - IrrXML found -# IRRXML_INCLUDE_DIR - Headers location -# IRRXML_LIBRARY - IrrXML main library - -find_path(IRRXML_INCLUDE_DIR irrXML.h - PATH_SUFFIXES include/irrlicht include/irrxml) -find_library(IRRXML_LIBRARY IrrXML) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(IrrXML REQUIRED_VARS IRRXML_INCLUDE_DIR IRRXML_LIBRARY) - - -mark_as_advanced(IRRXML_INCLUDE_DIR IRRXML_LIBRARY) diff --git a/cmake/HunterGate.cmake b/cmake-modules/HunterGate.cmake similarity index 100% rename from cmake/HunterGate.cmake rename to cmake-modules/HunterGate.cmake diff --git a/cmake-modules/assimp-hunter-config.cmake.in b/cmake-modules/assimp-hunter-config.cmake.in new file mode 100644 index 000000000..1988f7e7d --- /dev/null +++ b/cmake-modules/assimp-hunter-config.cmake.in @@ -0,0 +1,19 @@ +@PACKAGE_INIT@ + +find_package(RapidJSON CONFIG REQUIRED) +find_package(ZLIB CONFIG REQUIRED) +find_package(utf8cpp CONFIG REQUIRED) +find_package(minizip CONFIG REQUIRED) +find_package(openddlparser CONFIG REQUIRED) +find_package(poly2tri CONFIG REQUIRED) +find_package(polyclipping CONFIG REQUIRED) +find_package(zip CONFIG REQUIRED) +find_package(pugixml CONFIG REQUIRED) +find_package(stb CONFIG REQUIRED) + +if(@ASSIMP_BUILD_DRACO@) + find_package(draco CONFIG REQUIRED) +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/cmake/assimp-plain-config.cmake.in b/cmake-modules/assimp-plain-config.cmake.in similarity index 100% rename from cmake/assimp-plain-config.cmake.in rename to cmake-modules/assimp-plain-config.cmake.in diff --git a/cmake/assimp-hunter-config.cmake.in b/cmake/assimp-hunter-config.cmake.in deleted file mode 100644 index 91efcbf24..000000000 --- a/cmake/assimp-hunter-config.cmake.in +++ /dev/null @@ -1,18 +0,0 @@ -@PACKAGE_INIT@ - -find_package(RapidJSON CONFIG REQUIRED) -find_package(ZLIB CONFIG REQUIRED) -find_package(utf8cpp CONFIG REQUIRED) -find_package(minizip CONFIG REQUIRED) -find_package(openddlparser CONFIG REQUIRED) -find_package(poly2tri CONFIG REQUIRED) -find_package(polyclipping CONFIG REQUIRED) -find_package(zip CONFIG REQUIRED) -find_package(pugixml CONFIG REQUIRED) - -if(@ASSIMP_BUILD_DRACO@) - find_package(draco CONFIG REQUIRED) -endif() - -include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake") -check_required_components("@PROJECT_NAME@") diff --git a/code/AssetLib/3DS/3DSConverter.cpp b/code/AssetLib/3DS/3DSConverter.cpp index aca16b0d6..add1553bc 100644 --- a/code/AssetLib/3DS/3DSConverter.cpp +++ b/code/AssetLib/3DS/3DSConverter.cpp @@ -68,8 +68,8 @@ void Discreet3DSImporter::ReplaceDefaultMaterial() { unsigned int idx(NotSet); for (unsigned int i = 0; i < mScene->mMaterials.size(); ++i) { std::string s = mScene->mMaterials[i].mName; - for (std::string::iterator it = s.begin(); it != s.end(); ++it) { - *it = static_cast(::tolower(static_cast(*it))); + for (char & it : s) { + it = static_cast(::tolower(static_cast(it))); } if (std::string::npos == s.find("default")) continue; @@ -79,12 +79,7 @@ void Discreet3DSImporter::ReplaceDefaultMaterial() { mScene->mMaterials[i].mDiffuse.r != mScene->mMaterials[i].mDiffuse.b) continue; - if (mScene->mMaterials[i].sTexDiffuse.mMapName.length() != 0 || - mScene->mMaterials[i].sTexBump.mMapName.length() != 0 || - mScene->mMaterials[i].sTexOpacity.mMapName.length() != 0 || - mScene->mMaterials[i].sTexEmissive.mMapName.length() != 0 || - mScene->mMaterials[i].sTexSpecular.mMapName.length() != 0 || - mScene->mMaterials[i].sTexShininess.mMapName.length() != 0) { + if (ContainsTextures(i)) { continue; } idx = i; diff --git a/code/AssetLib/3DS/3DSExporter.cpp b/code/AssetLib/3DS/3DSExporter.cpp index 92a6d5aa7..0beecd563 100644 --- a/code/AssetLib/3DS/3DSExporter.cpp +++ b/code/AssetLib/3DS/3DSExporter.cpp @@ -291,7 +291,7 @@ void Discreet3DSExporter::WriteMaterials() { ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_SPECULAR); WriteColor(color); } - + if (mat.Get(AI_MATKEY_COLOR_AMBIENT, color) == AI_SUCCESS) { ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_AMBIENT); WriteColor(color); diff --git a/code/AssetLib/3DS/3DSHelper.h b/code/AssetLib/3DS/3DSHelper.h index 1930c0c40..e8efbf949 100644 --- a/code/AssetLib/3DS/3DSHelper.h +++ b/code/AssetLib/3DS/3DSHelper.h @@ -348,16 +348,16 @@ struct Texture { // empty } - Texture(Texture &&other) AI_NO_EXCEPT : mTextureBlend(std::move(other.mTextureBlend)), + Texture(Texture &&other) AI_NO_EXCEPT : mTextureBlend(other.mTextureBlend), mMapName(std::move(other.mMapName)), - mOffsetU(std::move(other.mOffsetU)), - mOffsetV(std::move(other.mOffsetV)), - mScaleU(std::move(other.mScaleU)), - mScaleV(std::move(other.mScaleV)), - mRotation(std::move(other.mRotation)), - mMapMode(std::move(other.mMapMode)), - bPrivate(std::move(other.bPrivate)), - iUVSrc(std::move(other.iUVSrc)) { + mOffsetU(other.mOffsetU), + mOffsetV(other.mOffsetV), + mScaleU(other.mScaleU), + mScaleV(other.mScaleV), + mRotation(other.mRotation), + mMapMode(other.mMapMode), + bPrivate(other.bPrivate), + iUVSrc(other.iUVSrc) { // empty } @@ -366,16 +366,16 @@ struct Texture { return *this; } - mTextureBlend = std::move(other.mTextureBlend); + mTextureBlend = other.mTextureBlend; mMapName = std::move(other.mMapName); - mOffsetU = std::move(other.mOffsetU); - mOffsetV = std::move(other.mOffsetV); - mScaleU = std::move(other.mScaleU); - mScaleV = std::move(other.mScaleV); - mRotation = std::move(other.mRotation); - mMapMode = std::move(other.mMapMode); - bPrivate = std::move(other.bPrivate); - iUVSrc = std::move(other.iUVSrc); + mOffsetU = other.mOffsetU; + mOffsetV = other.mOffsetV; + mScaleU = other.mScaleU; + mScaleV = other.mScaleV; + mRotation = other.mRotation; + mMapMode = other.mMapMode; + bPrivate = other.bPrivate; + iUVSrc = other.iUVSrc; return *this; } @@ -461,13 +461,13 @@ struct Material { //! Move constructor. This is explicitly written because MSVC doesn't support defaulting it Material(Material &&other) AI_NO_EXCEPT : mName(std::move(other.mName)), - mDiffuse(std::move(other.mDiffuse)), - mSpecularExponent(std::move(other.mSpecularExponent)), - mShininessStrength(std::move(other.mShininessStrength)), - mSpecular(std::move(other.mSpecular)), - mAmbient(std::move(other.mAmbient)), - mShading(std::move(other.mShading)), - mTransparency(std::move(other.mTransparency)), + mDiffuse(other.mDiffuse), + mSpecularExponent(other.mSpecularExponent), + mShininessStrength(other.mShininessStrength), + mSpecular(other.mSpecular), + mAmbient(other.mAmbient), + mShading(other.mShading), + mTransparency(other.mTransparency), sTexDiffuse(std::move(other.sTexDiffuse)), sTexOpacity(std::move(other.sTexOpacity)), sTexSpecular(std::move(other.sTexSpecular)), @@ -475,10 +475,10 @@ struct Material { sTexBump(std::move(other.sTexBump)), sTexEmissive(std::move(other.sTexEmissive)), sTexShininess(std::move(other.sTexShininess)), - mBumpHeight(std::move(other.mBumpHeight)), - mEmissive(std::move(other.mEmissive)), + mBumpHeight(other.mBumpHeight), + mEmissive(other.mEmissive), sTexAmbient(std::move(other.sTexAmbient)), - mTwoSided(std::move(other.mTwoSided)) { + mTwoSided(other.mTwoSided) { // empty } @@ -488,13 +488,13 @@ struct Material { } mName = std::move(other.mName); - mDiffuse = std::move(other.mDiffuse); - mSpecularExponent = std::move(other.mSpecularExponent); - mShininessStrength = std::move(other.mShininessStrength), - mSpecular = std::move(other.mSpecular); - mAmbient = std::move(other.mAmbient); - mShading = std::move(other.mShading); - mTransparency = std::move(other.mTransparency); + mDiffuse = other.mDiffuse; + mSpecularExponent = other.mSpecularExponent; + mShininessStrength = other.mShininessStrength, + mSpecular = other.mSpecular; + mAmbient = other.mAmbient; + mShading = other.mShading; + mTransparency = other.mTransparency; sTexDiffuse = std::move(other.sTexDiffuse); sTexOpacity = std::move(other.sTexOpacity); sTexSpecular = std::move(other.sTexSpecular); @@ -502,10 +502,10 @@ struct Material { sTexBump = std::move(other.sTexBump); sTexEmissive = std::move(other.sTexEmissive); sTexShininess = std::move(other.sTexShininess); - mBumpHeight = std::move(other.mBumpHeight); - mEmissive = std::move(other.mEmissive); + mBumpHeight = other.mBumpHeight; + mEmissive = other.mEmissive; sTexAmbient = std::move(other.sTexAmbient); - mTwoSided = std::move(other.mTwoSided); + mTwoSided = other.mTwoSided; return *this; } diff --git a/code/AssetLib/3DS/3DSLoader.h b/code/AssetLib/3DS/3DSLoader.h index 2091fbeb7..04dcac237 100644 --- a/code/AssetLib/3DS/3DSLoader.h +++ b/code/AssetLib/3DS/3DSLoader.h @@ -208,6 +208,15 @@ protected: */ void ReplaceDefaultMaterial(); + bool ContainsTextures(unsigned int i) const { + return !mScene->mMaterials[i].sTexDiffuse.mMapName.empty() || + !mScene->mMaterials[i].sTexBump.mMapName.empty() || + !mScene->mMaterials[i].sTexOpacity.mMapName.empty() || + !mScene->mMaterials[i].sTexEmissive.mMapName.empty() || + !mScene->mMaterials[i].sTexSpecular.mMapName.empty() || + !mScene->mMaterials[i].sTexShininess.mMapName.empty() ; + } + // ------------------------------------------------------------------- /** Convert the whole scene */ diff --git a/code/AssetLib/3MF/3MFTypes.h b/code/AssetLib/3MF/3MFTypes.h new file mode 100644 index 000000000..c4e9e4243 --- /dev/null +++ b/code/AssetLib/3MF/3MFTypes.h @@ -0,0 +1,165 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2021, 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. + +---------------------------------------------------------------------- +*/ +#pragma once + +#include +#include +#include +#include +#include + +struct aiMaterial; +struct aiMesh; + +namespace Assimp { +namespace D3MF { + +enum class ResourceType { + RT_Object, + RT_BaseMaterials, + RT_EmbeddedTexture2D, + RT_Texture2DGroup, + RT_Unknown +}; // To be extended with other resource types (eg. material extension resources like Texture2d, Texture2dGroup...) + +class Resource { +public: + int mId; + + Resource(int id) : + mId(id) { + // empty + } + + virtual ~Resource() { + // empty + } + + virtual ResourceType getType() const { + return ResourceType::RT_Unknown; + } +}; + +class EmbeddedTexture : public Resource { +public: + std::string mPath; + std::string mContentType; + std::string mTilestyleU; + std::string mTilestyleV; + std::vector mBuffer; + + EmbeddedTexture(int id) : + Resource(id), + mPath(), + mContentType(), + mTilestyleU(), + mTilestyleV() { + // empty + } + + ~EmbeddedTexture() = default; + + ResourceType getType() const override { + return ResourceType::RT_EmbeddedTexture2D; + } +}; + +class Texture2DGroup : public Resource { +public: + std::vector mTex2dCoords; + int mTexId; + Texture2DGroup(int id) : + Resource(id), + mTexId(-1) { + // empty + } + + ~Texture2DGroup() = default; + + ResourceType getType() const override { + return ResourceType::RT_Texture2DGroup; + } +}; + +class BaseMaterials : public Resource { +public: + std::vector mMaterialIndex; + + BaseMaterials(int id) : + Resource(id), + mMaterialIndex() { + // empty + } + + ~BaseMaterials() = default; + + ResourceType getType() const override { + return ResourceType::RT_BaseMaterials; + } +}; + +struct Component { + int mObjectId; + aiMatrix4x4 mTransformation; +}; + +class Object : public Resource { +public: + std::vector mMeshes; + std::vector mMeshIndex; + std::vector mComponents; + std::string mName; + + Object(int id) : + Resource(id), + mName(std::string("Object_") + ai_to_string(id)) { + // empty + } + + ~Object() = default; + + ResourceType getType() const override { + return ResourceType::RT_Object; + } +}; + +} // namespace D3MF +} // namespace Assimp diff --git a/code/AssetLib/3MF/3MFXmlTags.h b/code/AssetLib/3MF/3MFXmlTags.h index d447556d6..a6e9758c1 100644 --- a/code/AssetLib/3MF/3MFXmlTags.h +++ b/code/AssetLib/3MF/3MFXmlTags.h @@ -80,13 +80,21 @@ namespace XmlTag { const char* const item = "item"; const char* const objectid = "objectid"; const char* const transform = "transform"; + const char *const path = "path"; // Material definitions const char* const basematerials = "basematerials"; - const char* const basematerials_id = "id"; const char* const basematerials_base = "base"; const char* const basematerials_name = "name"; const char* const basematerials_displaycolor = "displaycolor"; + const char* const texture_2d = "m:texture2d"; + const char *const texture_group = "m:texture2dgroup"; + const char *const texture_content_type = "contenttype"; + const char *const texture_tilestyleu = "tilestyleu"; + const char *const texture_tilestylev = "tilestylev"; + const char *const texture_2d_coord = "m:tex2coord"; + const char *const texture_cuurd_u = "u"; + const char *const texture_cuurd_v = "v"; // Meta info tags const char* const CONTENT_TYPES_ARCHIVE = "[Content_Types].xml"; @@ -103,7 +111,7 @@ namespace XmlTag { const char* const PACKAGE_TEXTURE_RELATIONSHIP_TYPE = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dtexture"; const char* const PACKAGE_CORE_PROPERTIES_RELATIONSHIP_TYPE = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"; const char* const PACKAGE_THUMBNAIL_RELATIONSHIP_TYPE = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail"; - } +} } // Namespace D3MF } // Namespace Assimp diff --git a/code/AssetLib/3MF/D3MFImporter.cpp b/code/AssetLib/3MF/D3MFImporter.cpp index 747af7cfc..58dde9738 100644 --- a/code/AssetLib/3MF/D3MFImporter.cpp +++ b/code/AssetLib/3MF/D3MFImporter.cpp @@ -44,6 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "D3MFImporter.h" #include "3MFXmlTags.h" #include "D3MFOpcPackage.h" +#include "XmlSerializer.h" #include #include @@ -61,513 +62,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include -#include +#include namespace Assimp { -namespace D3MF { - -enum class ResourceType { - RT_Object, - RT_BaseMaterials, - RT_Unknown -}; // To be extended with other resource types (eg. material extension resources like Texture2d, Texture2dGroup...) - -class Resource { -public: - int mId; - - Resource(int id) : - mId(id) { - // empty - } - - virtual ~Resource() { - // empty - } - - virtual ResourceType getType() const { - return ResourceType::RT_Unknown; - } -}; - -class BaseMaterials : public Resource { -public: - std::vector mMaterials; - std::vector mMaterialIndex; - - BaseMaterials(int id) : - Resource(id), - mMaterials(), - mMaterialIndex() { - // empty - } - - ~BaseMaterials() = default; - - ResourceType getType() const override { - return ResourceType::RT_BaseMaterials; - } -}; - -struct Component { - int mObjectId; - aiMatrix4x4 mTransformation; -}; - -class Object : public Resource { -public: - std::vector mMeshes; - std::vector mMeshIndex; - std::vector mComponents; - std::string mName; - - Object(int id) : - Resource(id), - mName(std::string("Object_") + ai_to_string(id)) { - // empty - } - - ~Object() = default; - - ResourceType getType() const override { - return ResourceType::RT_Object; - } -}; - -class XmlSerializer { -public: - XmlSerializer(XmlParser *xmlParser) : - mResourcesDictionnary(), - mMaterialCount(0), - mMeshCount(0), - mXmlParser(xmlParser) { - // empty - } - - ~XmlSerializer() { - for (auto it = mResourcesDictionnary.begin(); it != mResourcesDictionnary.end(); ++it ) { - delete it->second; - } - } - - void ImportXml(aiScene *scene) { - if (nullptr == scene) { - return; - } - - scene->mRootNode = new aiNode(XmlTag::RootTag); - - XmlNode node = mXmlParser->getRootNode().child(XmlTag::model); - if (node.empty()) { - return; - } - XmlNode resNode = node.child(XmlTag::resources); - for (auto ¤tNode : resNode.children()) { - const std::string currentNodeName = currentNode.name(); - if (currentNodeName == XmlTag::object) { - ReadObject(currentNode); - } else if (currentNodeName == XmlTag::basematerials) { - ReadBaseMaterials(currentNode); - } else if (currentNodeName == XmlTag::meta) { - ReadMetadata(currentNode); - } - } - - XmlNode buildNode = node.child(XmlTag::build); - for (auto ¤tNode : buildNode.children()) { - const std::string currentNodeName = currentNode.name(); - if (currentNodeName == XmlTag::item) { - int objectId = -1; - std::string transformationMatrixStr; - aiMatrix4x4 transformationMatrix; - getNodeAttribute(currentNode, D3MF::XmlTag::objectid, objectId); - bool hasTransform = getNodeAttribute(currentNode, D3MF::XmlTag::transform, transformationMatrixStr); - - auto it = mResourcesDictionnary.find(objectId); - if (it != mResourcesDictionnary.end() && it->second->getType() == ResourceType::RT_Object) { - Object *obj = static_cast(it->second); - if (hasTransform) { - transformationMatrix = parseTransformMatrix(transformationMatrixStr); - } - - addObjectToNode(scene->mRootNode, obj, transformationMatrix); - } - } - } - - // import the metadata - if (!mMetaData.empty()) { - const size_t numMeta = mMetaData.size(); - scene->mMetaData = aiMetadata::Alloc(static_cast(numMeta)); - for (size_t i = 0; i < numMeta; ++i) { - aiString val(mMetaData[i].value); - scene->mMetaData->Set(static_cast(i), mMetaData[i].name, val); - } - } - - // import the meshes - scene->mNumMeshes = static_cast(mMeshCount); - if (scene->mNumMeshes != 0) { - scene->mMeshes = new aiMesh *[scene->mNumMeshes](); - for (auto it = mResourcesDictionnary.begin(); it != mResourcesDictionnary.end(); ++it) { - if (it->second->getType() == ResourceType::RT_Object) { - Object *obj = static_cast(it->second); - ai_assert(nullptr != obj); - for (unsigned int i = 0; i < obj->mMeshes.size(); ++i) { - scene->mMeshes[obj->mMeshIndex[i]] = obj->mMeshes[i]; - } - } - } - } - - // import the materials - scene->mNumMaterials = mMaterialCount; - if (scene->mNumMaterials != 0) { - scene->mMaterials = new aiMaterial *[scene->mNumMaterials]; - for (auto it = mResourcesDictionnary.begin(); it != mResourcesDictionnary.end(); ++it) { - if (it->second->getType() == ResourceType::RT_BaseMaterials) { - BaseMaterials *baseMaterials = static_cast(it->second); - for (unsigned int i = 0; i < baseMaterials->mMaterials.size(); ++i) { - scene->mMaterials[baseMaterials->mMaterialIndex[i]] = baseMaterials->mMaterials[i]; - } - } - } - } - } - -private: - void addObjectToNode(aiNode *parent, Object *obj, aiMatrix4x4 nodeTransform) { - ai_assert(nullptr != obj); - - aiNode *sceneNode = new aiNode(obj->mName); - sceneNode->mNumMeshes = static_cast(obj->mMeshes.size()); - sceneNode->mMeshes = new unsigned int[sceneNode->mNumMeshes]; - std::copy(obj->mMeshIndex.begin(), obj->mMeshIndex.end(), sceneNode->mMeshes); - - sceneNode->mTransformation = nodeTransform; - if (nullptr != parent) { - parent->addChildren(1, &sceneNode); - } - - for (size_t i = 0; i < obj->mComponents.size(); ++i) { - Component c = obj->mComponents[i]; - auto it = mResourcesDictionnary.find(c.mObjectId); - if (it != mResourcesDictionnary.end() && it->second->getType() == ResourceType::RT_Object) { - addObjectToNode(sceneNode, static_cast(it->second), c.mTransformation); - } - } - } - - bool getNodeAttribute(const XmlNode &node, const std::string &attribute, std::string &value) { - pugi::xml_attribute objectAttribute = node.attribute(attribute.c_str()); - if (!objectAttribute.empty()) { - value = objectAttribute.as_string(); - return true; - } - - return false; - } - - bool getNodeAttribute(const XmlNode &node, const std::string &attribute, int &value) { - std::string strValue; - bool ret = getNodeAttribute(node, attribute, strValue); - if (ret) { - value = std::atoi(strValue.c_str()); - return true; - } - - return false; - } - - aiMatrix4x4 parseTransformMatrix(std::string matrixStr) { - // split the string - std::vector numbers; - std::string currentNumber; - for (size_t i = 0; i < matrixStr.size(); ++i) { - const char c = matrixStr[i]; - if (c == ' ') { - if (currentNumber.size() > 0) { - float f = std::stof(currentNumber); - numbers.push_back(f); - currentNumber.clear(); - } - } else { - currentNumber.push_back(c); - } - } - if (currentNumber.size() > 0) { - const float f = std::stof(currentNumber); - numbers.push_back(f); - } - - aiMatrix4x4 transformMatrix; - transformMatrix.a1 = numbers[0]; - transformMatrix.b1 = numbers[1]; - transformMatrix.c1 = numbers[2]; - transformMatrix.d1 = 0; - - transformMatrix.a2 = numbers[3]; - transformMatrix.b2 = numbers[4]; - transformMatrix.c2 = numbers[5]; - transformMatrix.d2 = 0; - - transformMatrix.a3 = numbers[6]; - transformMatrix.b3 = numbers[7]; - transformMatrix.c3 = numbers[8]; - transformMatrix.d3 = 0; - - transformMatrix.a4 = numbers[9]; - transformMatrix.b4 = numbers[10]; - transformMatrix.c4 = numbers[11]; - transformMatrix.d4 = 1; - - return transformMatrix; - } - - void ReadObject(XmlNode &node) { - int id = -1, pid = -1, pindex = -1; - bool hasId = getNodeAttribute(node, XmlTag::id, id); - bool hasPid = getNodeAttribute(node, XmlTag::pid, pid); - bool hasPindex = getNodeAttribute(node, XmlTag::pindex, pindex); - if (!hasId) { - return; - } - - Object *obj = new Object(id); - - for (XmlNode ¤tNode : node.children()) { - const std::string ¤tName = currentNode.name(); - if (currentName == D3MF::XmlTag::mesh) { - auto mesh = ReadMesh(currentNode); - mesh->mName.Set(ai_to_string(id)); - - if (hasPid) { - auto it = mResourcesDictionnary.find(pid); - if (hasPindex && it != mResourcesDictionnary.end() && it->second->getType() == ResourceType::RT_BaseMaterials) { - BaseMaterials *materials = static_cast(it->second); - mesh->mMaterialIndex = materials->mMaterialIndex[pindex]; - } - } - - obj->mMeshes.push_back(mesh); - obj->mMeshIndex.push_back(mMeshCount); - mMeshCount++; - } else if (currentName == D3MF::XmlTag::components) { - for (XmlNode ¤tSubNode : currentNode.children()) { - const std::string subNodeName = currentSubNode.name(); - if (subNodeName == D3MF::XmlTag::component) { - int objectId = -1; - std::string componentTransformStr; - aiMatrix4x4 componentTransform; - if (getNodeAttribute(currentSubNode, D3MF::XmlTag::transform, componentTransformStr)) { - componentTransform = parseTransformMatrix(componentTransformStr); - } - - if (getNodeAttribute(currentSubNode, D3MF::XmlTag::objectid, objectId)) { - obj->mComponents.push_back({ objectId, componentTransform }); - } - } - } - } - } - - mResourcesDictionnary.insert(std::make_pair(id, obj)); - } - - aiMesh *ReadMesh(XmlNode &node) { - aiMesh *mesh = new aiMesh(); - - for (XmlNode ¤tNode : node.children()) { - const std::string currentName = currentNode.name(); - if (currentName == XmlTag::vertices) { - ImportVertices(currentNode, mesh); - } else if (currentName == XmlTag::triangles) { - ImportTriangles(currentNode, mesh); - } - } - - return mesh; - } - - void ReadMetadata(XmlNode &node) { - pugi::xml_attribute attribute = node.attribute(D3MF::XmlTag::meta_name); - const std::string name = attribute.as_string(); - const std::string value = node.value(); - if (name.empty()) { - return; - } - - MetaEntry entry; - entry.name = name; - entry.value = value; - mMetaData.push_back(entry); - } - - void ImportVertices(XmlNode &node, aiMesh *mesh) { - std::vector vertices; - for (XmlNode ¤tNode : node.children()) { - const std::string currentName = currentNode.name(); - if (currentName == XmlTag::vertex) { - vertices.push_back(ReadVertex(currentNode)); - } - } - - mesh->mNumVertices = static_cast(vertices.size()); - mesh->mVertices = new aiVector3D[mesh->mNumVertices]; - std::copy(vertices.begin(), vertices.end(), mesh->mVertices); - } - - aiVector3D ReadVertex(XmlNode &node) { - aiVector3D vertex; - vertex.x = ai_strtof(node.attribute(XmlTag::x).as_string(), nullptr); - vertex.y = ai_strtof(node.attribute(XmlTag::y).as_string(), nullptr); - vertex.z = ai_strtof(node.attribute(XmlTag::z).as_string(), nullptr); - - return vertex; - } - - void ImportTriangles(XmlNode &node, aiMesh *mesh) { - std::vector faces; - for (XmlNode ¤tNode : node.children()) { - const std::string currentName = currentNode.name(); - if (currentName == XmlTag::triangle) { - aiFace face = ReadTriangle(currentNode); - faces.push_back(face); - - int pid = 0, p1 = 0; - bool hasPid = getNodeAttribute(currentNode, D3MF::XmlTag::pid, pid); - bool hasP1 = getNodeAttribute(currentNode, D3MF::XmlTag::p1, p1); - - if (hasPid && hasP1) { - auto it = mResourcesDictionnary.find(pid); - if (it != mResourcesDictionnary.end()) { - if (it->second->getType() == ResourceType::RT_BaseMaterials) { - BaseMaterials *baseMaterials = static_cast(it->second); - mesh->mMaterialIndex = baseMaterials->mMaterialIndex[p1]; - } - // TODO: manage the separation into several meshes if the triangles of the mesh do not all refer to the same material - } - } - } - } - - mesh->mNumFaces = static_cast(faces.size()); - mesh->mFaces = new aiFace[mesh->mNumFaces]; - mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; - - std::copy(faces.begin(), faces.end(), mesh->mFaces); - } - - aiFace ReadTriangle(XmlNode &node) { - aiFace face; - - face.mNumIndices = 3; - face.mIndices = new unsigned int[face.mNumIndices]; - face.mIndices[0] = static_cast(std::atoi(node.attribute(XmlTag::v1).as_string())); - face.mIndices[1] = static_cast(std::atoi(node.attribute(XmlTag::v2).as_string())); - face.mIndices[2] = static_cast(std::atoi(node.attribute(XmlTag::v3).as_string())); - - return face; - } - - void ReadBaseMaterials(XmlNode &node) { - int id = -1; - if (getNodeAttribute(node, D3MF::XmlTag::basematerials_id, id)) { - BaseMaterials *baseMaterials = new BaseMaterials(id); - - for (XmlNode ¤tNode : node.children()) { - const std::string currentName = currentNode.name(); - if (currentName == XmlTag::basematerials_base) { - baseMaterials->mMaterialIndex.push_back(mMaterialCount); - baseMaterials->mMaterials.push_back(readMaterialDef(currentNode, id)); - ++mMaterialCount; - } - } - - mResourcesDictionnary.insert(std::make_pair(id, baseMaterials)); - } - } - - bool parseColor(const char *color, aiColor4D &diffuse) { - if (nullptr == color) { - return false; - } - - //format of the color string: #RRGGBBAA or #RRGGBB (3MF Core chapter 5.1.1) - const size_t len = strlen(color); - if (9 != len && 7 != len) { - return false; - } - - const char *buf(color); - if ('#' != buf[0]) { - return false; - } - - char r[3] = { buf[1], buf[2], '\0' }; - diffuse.r = static_cast(strtol(r, nullptr, 16)) / ai_real(255.0); - - char g[3] = { buf[3], buf[4], '\0' }; - diffuse.g = static_cast(strtol(g, nullptr, 16)) / ai_real(255.0); - - char b[3] = { buf[5], buf[6], '\0' }; - diffuse.b = static_cast(strtol(b, nullptr, 16)) / ai_real(255.0); - - if (7 == len) - return true; - - char a[3] = { buf[7], buf[8], '\0' }; - diffuse.a = static_cast(strtol(a, nullptr, 16)) / ai_real(255.0); - - return true; - } - - void assignDiffuseColor(XmlNode &node, aiMaterial *mat) { - const char *color = node.attribute(XmlTag::basematerials_displaycolor).as_string(); - aiColor4D diffuse; - if (parseColor(color, diffuse)) { - mat->AddProperty(&diffuse, 1, AI_MATKEY_COLOR_DIFFUSE); - } - } - - aiMaterial *readMaterialDef(XmlNode &node, unsigned int basematerialsId) { - aiMaterial *material = new aiMaterial(); - material->mNumProperties = 0; - std::string name; - bool hasName = getNodeAttribute(node, D3MF::XmlTag::basematerials_name, name); - - std::string stdMaterialName; - const std::string strId(ai_to_string(basematerialsId)); - stdMaterialName += "id"; - stdMaterialName += strId; - stdMaterialName += "_"; - if (hasName) { - stdMaterialName += std::string(name); - } else { - stdMaterialName += "basemat_"; - stdMaterialName += ai_to_string(mMaterialCount - basematerialsId); - } - - aiString assimpMaterialName(stdMaterialName); - material->AddProperty(&assimpMaterialName, AI_MATKEY_NAME); - - assignDiffuseColor(node, material); - - return material; - } - -private: - struct MetaEntry { - std::string name; - std::string value; - }; - std::vector mMetaData; - std::map mResourcesDictionnary; - unsigned int mMaterialCount, mMeshCount; - XmlParser *mXmlParser; -}; - -} //namespace D3MF using namespace D3MF; @@ -597,7 +94,9 @@ bool D3MFImporter::CanRead(const std::string &filename, IOSystem *pIOHandler, bo const std::string extension(GetExtension(filename)); if (extension == desc.mFileExtensions) { return true; - } else if (!extension.length() || checkSig) { + } + + if (!extension.length() || checkSig) { if (nullptr == pIOHandler) { return false; } @@ -611,7 +110,7 @@ bool D3MFImporter::CanRead(const std::string &filename, IOSystem *pIOHandler, bo return false; } -void D3MFImporter::SetupProperties(const Importer * /*pImp*/) { +void D3MFImporter::SetupProperties(const Importer*) { // empty } @@ -626,6 +125,15 @@ void D3MFImporter::InternReadFile(const std::string &filename, aiScene *pScene, if (xmlParser.parse(opcPackage.RootStream())) { XmlSerializer xmlSerializer(&xmlParser); xmlSerializer.ImportXml(pScene); + + const std::vector &tex = opcPackage.GetEmbeddedTextures(); + if (!tex.empty()) { + pScene->mNumTextures = static_cast(tex.size()); + pScene->mTextures = new aiTexture *[pScene->mNumTextures]; + for (unsigned int i = 0; i < pScene->mNumTextures; ++i) { + pScene->mTextures[i] = tex[i]; + } + } } } diff --git a/code/AssetLib/3MF/D3MFImporter.h b/code/AssetLib/3MF/D3MFImporter.h index 811c463b6..2b37010d9 100644 --- a/code/AssetLib/3MF/D3MFImporter.h +++ b/code/AssetLib/3MF/D3MFImporter.h @@ -4,7 +4,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2021, assimp team - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -47,17 +46,40 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { +// --------------------------------------------------------------------------- /// @brief The 3MF-importer class. +/// +/// Implements the basic topology import and embedded textures. +// --------------------------------------------------------------------------- class D3MFImporter : public BaseImporter { public: + /// @brief The default class constructor. D3MFImporter(); - ~D3MFImporter(); - bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const; - void SetupProperties(const Importer *pImp); - const aiImporterDesc *GetInfo() const; + + /// @brief The class destructor. + ~D3MFImporter() override; + + /// @brief Performs the data format detection. + /// @param pFile The filename to check. + /// @param pIOHandler The used IO-System. + /// @param checkSig true for signature checking. + /// @return true for can be loaded, false for not. + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override; + + /// @brief Not used + /// @param pImp Not used + void SetupProperties(const Importer *pImp) override; + + /// @brief The importer description getter. + /// @return The info + const aiImporterDesc *GetInfo() const override; protected: - void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler); + /// @brief Internal read function, performs the file parsing. + /// @param pFile The filename + /// @param pScene The scene to load in. + /// @param pIOHandler The io-system + void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; }; } // Namespace Assimp diff --git a/code/AssetLib/3MF/D3MFOpcPackage.cpp b/code/AssetLib/3MF/D3MFOpcPackage.cpp index dbf4f2e10..c29cec368 100644 --- a/code/AssetLib/3MF/D3MFOpcPackage.cpp +++ b/code/AssetLib/3MF/D3MFOpcPackage.cpp @@ -43,14 +43,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "D3MFOpcPackage.h" #include - #include #include #include #include #include #include - +#include #include "3MFXmlTags.h" #include #include @@ -64,11 +63,12 @@ namespace Assimp { namespace D3MF { // ------------------------------------------------------------------------------------------------ -typedef std::shared_ptr OpcPackageRelationshipPtr; +using OpcPackageRelationshipPtr = std::shared_ptr; class OpcPackageRelationshipReader { public: - OpcPackageRelationshipReader(XmlParser &parser) { + OpcPackageRelationshipReader(XmlParser &parser) : + m_relationShips() { XmlNode root = parser.getRootNode(); ParseRootNode(root); } @@ -91,6 +91,7 @@ public: if (relPtr->id.empty() || relPtr->type.empty() || relPtr->target.empty()) { return false; } + return true; } @@ -100,7 +101,7 @@ public: } for (XmlNode currentNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) { - std::string name = currentNode.name(); + const std::string name = currentNode.name(); if (name == "Relationship") { OpcPackageRelationshipPtr relPtr(new OpcPackageRelationship()); relPtr->id = currentNode.attribute(XmlTag::RELS_ATTRIB_ID).as_string(); @@ -116,11 +117,23 @@ public: std::vector m_relationShips; }; +static bool IsEmbeddedTexture( const std::string &filename ) { + const std::string extension = BaseImporter::GetExtension(filename); + if (extension == "jpg" || extension == "png") { + std::string::size_type pos = filename.find("thumbnail"); + if (pos == std::string::npos) { + return false; + } + return true; + } + + return false; +} // ------------------------------------------------------------------------------------------------ D3MFOpcPackage::D3MFOpcPackage(IOSystem *pIOHandler, const std::string &rFile) : mRootStream(nullptr), mZipArchive() { - mZipArchive.reset(new ZipArchiveIOSystem(pIOHandler, rFile)); + mZipArchive = new ZipArchiveIOSystem(pIOHandler, rFile); if (!mZipArchive->isOpen()) { throw DeadlyImportError("Failed to open file ", rFile, "."); } @@ -141,13 +154,13 @@ D3MFOpcPackage::D3MFOpcPackage(IOSystem *pIOHandler, const std::string &rFile) : } std::string rootFile = ReadPackageRootRelationship(fileStream); - if (rootFile.size() > 0 && rootFile[0] == '/') { + if (!rootFile.empty() && rootFile[0] == '/') { rootFile = rootFile.substr(1); if (rootFile[0] == '/') { // deal with zip-bug rootFile = rootFile.substr(1); } - } + } ASSIMP_LOG_VERBOSE_DEBUG(rootFile); @@ -158,9 +171,12 @@ D3MFOpcPackage::D3MFOpcPackage(IOSystem *pIOHandler, const std::string &rFile) : if (nullptr == mRootStream) { throw DeadlyImportError("Cannot open root-file in archive : " + rootFile); } - } else if (file == D3MF::XmlTag::CONTENT_TYPES_ARCHIVE) { ASSIMP_LOG_WARN("Ignored file of unsupported type CONTENT_TYPES_ARCHIVES", file); + } else if (IsEmbeddedTexture(file)) { + IOStream *fileStream = mZipArchive->Open(file.c_str()); + LoadEmbeddedTextures(fileStream, file); + mZipArchive->Close(fileStream); } else { ASSIMP_LOG_WARN("Ignored file of unknown type: ", file); } @@ -169,20 +185,26 @@ D3MFOpcPackage::D3MFOpcPackage(IOSystem *pIOHandler, const std::string &rFile) : D3MFOpcPackage::~D3MFOpcPackage() { mZipArchive->Close(mRootStream); + delete mZipArchive; + mZipArchive = nullptr; } IOStream *D3MFOpcPackage::RootStream() const { return mRootStream; } -static const std::string ModelRef = "3D/3dmodel.model"; +const std::vector &D3MFOpcPackage::GetEmbeddedTextures() const { + return mEmbeddedTextures; +} + +static const char *const ModelRef = "3D/3dmodel.model"; bool D3MFOpcPackage::validate() { if (nullptr == mRootStream || nullptr == mZipArchive) { return false; } - return mZipArchive->Exists(ModelRef.c_str()); + return mZipArchive->Exists(ModelRef); } std::string D3MFOpcPackage::ReadPackageRootRelationship(IOStream *stream) { @@ -204,6 +226,31 @@ std::string D3MFOpcPackage::ReadPackageRootRelationship(IOStream *stream) { return (*itr)->target; } +void D3MFOpcPackage::LoadEmbeddedTextures(IOStream *fileStream, const std::string &filename) { + if (nullptr == fileStream) { + return; + } + + const size_t size = fileStream->FileSize(); + if (0 == size) { + return; + } + + unsigned char *data = new unsigned char[size]; + fileStream->Read(data, 1, size); + aiTexture *texture = new aiTexture; + std::string embName = "*" + filename; + texture->mFilename.Set(embName.c_str()); + texture->mWidth = static_cast(size); + texture->mHeight = 0; + texture->achFormatHint[0] = 'p'; + texture->achFormatHint[1] = 'n'; + texture->achFormatHint[2] = 'g'; + texture->achFormatHint[3] = '\0'; + texture->pcData = (aiTexel*) data; + mEmbeddedTextures.emplace_back(texture); +} + } // Namespace D3MF } // Namespace Assimp diff --git a/code/AssetLib/3MF/D3MFOpcPackage.h b/code/AssetLib/3MF/D3MFOpcPackage.h index 22b4510d0..fda74a879 100644 --- a/code/AssetLib/3MF/D3MFOpcPackage.h +++ b/code/AssetLib/3MF/D3MFOpcPackage.h @@ -46,8 +46,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +struct aiTexture; + namespace Assimp { - class ZipArchiveIOSystem; + +class ZipArchiveIOSystem; namespace D3MF { @@ -63,16 +66,19 @@ public: ~D3MFOpcPackage(); IOStream* RootStream() const; bool validate(); + const std::vector &GetEmbeddedTextures() const; protected: std::string ReadPackageRootRelationship(IOStream* stream); + void LoadEmbeddedTextures(IOStream *fileStream, const std::string &filename); private: IOStream* mRootStream; - std::unique_ptr mZipArchive; + ZipArchiveIOSystem *mZipArchive; + std::vector mEmbeddedTextures; }; -} // Namespace D3MF -} // Namespace Assimp +} // namespace D3MF +} // namespace Assimp #endif // D3MFOPCPACKAGE_H diff --git a/code/AssetLib/3MF/XmlSerializer.cpp b/code/AssetLib/3MF/XmlSerializer.cpp new file mode 100644 index 000000000..7a33d08ed --- /dev/null +++ b/code/AssetLib/3MF/XmlSerializer.cpp @@ -0,0 +1,594 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2021, 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. + +---------------------------------------------------------------------- +*/ +#include "XmlSerializer.h" +#include "D3MFOpcPackage.h" +#include "3MFXmlTags.h" +#include "3MFTypes.h" +#include + +namespace Assimp { +namespace D3MF { + +static const int IdNotSet = -1; + +namespace { + +static const size_t ColRGBA_Len = 9; +static const size_t ColRGB_Len = 7; + +// format of the color string: #RRGGBBAA or #RRGGBB (3MF Core chapter 5.1.1) +bool validateColorString(const char *color) { + const size_t len = strlen(color); + if (ColRGBA_Len != len && ColRGB_Len != len) { + return false; + } + + return true; +} + +aiFace ReadTriangle(XmlNode &node) { + aiFace face; + + face.mNumIndices = 3; + face.mIndices = new unsigned int[face.mNumIndices]; + face.mIndices[0] = static_cast(std::atoi(node.attribute(XmlTag::v1).as_string())); + face.mIndices[1] = static_cast(std::atoi(node.attribute(XmlTag::v2).as_string())); + face.mIndices[2] = static_cast(std::atoi(node.attribute(XmlTag::v3).as_string())); + + return face; +} + +aiVector3D ReadVertex(XmlNode &node) { + aiVector3D vertex; + vertex.x = ai_strtof(node.attribute(XmlTag::x).as_string(), nullptr); + vertex.y = ai_strtof(node.attribute(XmlTag::y).as_string(), nullptr); + vertex.z = ai_strtof(node.attribute(XmlTag::z).as_string(), nullptr); + + return vertex; +} + +bool getNodeAttribute(const XmlNode &node, const std::string &attribute, std::string &value) { + pugi::xml_attribute objectAttribute = node.attribute(attribute.c_str()); + if (!objectAttribute.empty()) { + value = objectAttribute.as_string(); + return true; + } + + return false; +} + +bool getNodeAttribute(const XmlNode &node, const std::string &attribute, int &value) { + std::string strValue; + const bool ret = getNodeAttribute(node, attribute, strValue); + if (ret) { + value = std::atoi(strValue.c_str()); + return true; + } + + return false; +} + +aiMatrix4x4 parseTransformMatrix(std::string matrixStr) { + // split the string + std::vector numbers; + std::string currentNumber; + for (char c : matrixStr) { + if (c == ' ') { + if (!currentNumber.empty()) { + float f = std::stof(currentNumber); + numbers.push_back(f); + currentNumber.clear(); + } + } else { + currentNumber.push_back(c); + } + } + if (!currentNumber.empty()) { + const float f = std::stof(currentNumber); + numbers.push_back(f); + } + + aiMatrix4x4 transformMatrix; + transformMatrix.a1 = numbers[0]; + transformMatrix.b1 = numbers[1]; + transformMatrix.c1 = numbers[2]; + transformMatrix.d1 = 0; + + transformMatrix.a2 = numbers[3]; + transformMatrix.b2 = numbers[4]; + transformMatrix.c2 = numbers[5]; + transformMatrix.d2 = 0; + + transformMatrix.a3 = numbers[6]; + transformMatrix.b3 = numbers[7]; + transformMatrix.c3 = numbers[8]; + transformMatrix.d3 = 0; + + transformMatrix.a4 = numbers[9]; + transformMatrix.b4 = numbers[10]; + transformMatrix.c4 = numbers[11]; + transformMatrix.d4 = 1; + + return transformMatrix; +} + +bool parseColor(const char *color, aiColor4D &diffuse) { + if (nullptr == color) { + return false; + } + + if (!validateColorString(color)) { + return false; + } + + //const char *buf(color); + if ('#' != color[0]) { + return false; + } + + char r[3] = { color[1], color[2], '\0' }; + diffuse.r = static_cast(strtol(r, nullptr, 16)) / ai_real(255.0); + + char g[3] = { color[3], color[4], '\0' }; + diffuse.g = static_cast(strtol(g, nullptr, 16)) / ai_real(255.0); + + char b[3] = { color[5], color[6], '\0' }; + diffuse.b = static_cast(strtol(b, nullptr, 16)) / ai_real(255.0); + const size_t len = strlen(color); + if (ColRGB_Len == len) { + return true; + } + + char a[3] = { color[7], color[8], '\0' }; + diffuse.a = static_cast(strtol(a, nullptr, 16)) / ai_real(255.0); + + return true; +} + +void assignDiffuseColor(XmlNode &node, aiMaterial *mat) { + const char *color = node.attribute(XmlTag::basematerials_displaycolor).as_string(); + aiColor4D diffuse; + if (parseColor(color, diffuse)) { + mat->AddProperty(&diffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + } +} + +} // namespace + +XmlSerializer::XmlSerializer(XmlParser *xmlParser) : + mResourcesDictionnary(), + mMeshCount(0), + mXmlParser(xmlParser) { + ai_assert(nullptr != xmlParser); +} + +XmlSerializer::~XmlSerializer() { + for (auto &it : mResourcesDictionnary) { + delete it.second; + } +} + +void XmlSerializer::ImportXml(aiScene *scene) { + if (nullptr == scene) { + return; + } + + scene->mRootNode = new aiNode(XmlTag::RootTag); + XmlNode node = mXmlParser->getRootNode().child(XmlTag::model); + if (node.empty()) { + return; + } + + XmlNode resNode = node.child(XmlTag::resources); + for (auto ¤tNode : resNode.children()) { + const std::string currentNodeName = currentNode.name(); + if (currentNodeName == XmlTag::texture_2d) { + ReadEmbeddecTexture(currentNode); + } else if (currentNodeName == XmlTag::texture_group) { + ReadTextureGroup(currentNode); + } else if (currentNodeName == XmlTag::object) { + ReadObject(currentNode); + } else if (currentNodeName == XmlTag::basematerials) { + ReadBaseMaterials(currentNode); + } else if (currentNodeName == XmlTag::meta) { + ReadMetadata(currentNode); + } + } + StoreMaterialsInScene(scene); + XmlNode buildNode = node.child(XmlTag::build); + if (buildNode.empty()) { + return; + } + + for (auto ¤tNode : buildNode.children()) { + const std::string currentNodeName = currentNode.name(); + if (currentNodeName == XmlTag::item) { + int objectId = IdNotSet; + std::string transformationMatrixStr; + aiMatrix4x4 transformationMatrix; + getNodeAttribute(currentNode, D3MF::XmlTag::objectid, objectId); + bool hasTransform = getNodeAttribute(currentNode, D3MF::XmlTag::transform, transformationMatrixStr); + + auto it = mResourcesDictionnary.find(objectId); + if (it != mResourcesDictionnary.end() && it->second->getType() == ResourceType::RT_Object) { + Object *obj = static_cast(it->second); + if (hasTransform) { + transformationMatrix = parseTransformMatrix(transformationMatrixStr); + } + + addObjectToNode(scene->mRootNode, obj, transformationMatrix); + } + } + } + + // import the metadata + if (!mMetaData.empty()) { + const size_t numMeta = mMetaData.size(); + scene->mMetaData = aiMetadata::Alloc(static_cast(numMeta)); + for (size_t i = 0; i < numMeta; ++i) { + aiString val(mMetaData[i].value); + scene->mMetaData->Set(static_cast(i), mMetaData[i].name, val); + } + } + + // import the meshes, materials are already stored + scene->mNumMeshes = static_cast(mMeshCount); + if (scene->mNumMeshes != 0) { + scene->mMeshes = new aiMesh *[scene->mNumMeshes](); + for (auto &it : mResourcesDictionnary) { + if (it.second->getType() == ResourceType::RT_Object) { + Object *obj = static_cast(it.second); + ai_assert(nullptr != obj); + for (unsigned int i = 0; i < obj->mMeshes.size(); ++i) { + scene->mMeshes[obj->mMeshIndex[i]] = obj->mMeshes[i]; + } + } + } + } +} + +void XmlSerializer::addObjectToNode(aiNode *parent, Object *obj, aiMatrix4x4 nodeTransform) { + ai_assert(nullptr != obj); + + aiNode *sceneNode = new aiNode(obj->mName); + sceneNode->mNumMeshes = static_cast(obj->mMeshes.size()); + sceneNode->mMeshes = new unsigned int[sceneNode->mNumMeshes]; + std::copy(obj->mMeshIndex.begin(), obj->mMeshIndex.end(), sceneNode->mMeshes); + + sceneNode->mTransformation = nodeTransform; + if (nullptr != parent) { + parent->addChildren(1, &sceneNode); + } + + for (Assimp::D3MF::Component c : obj->mComponents) { + auto it = mResourcesDictionnary.find(c.mObjectId); + if (it != mResourcesDictionnary.end() && it->second->getType() == ResourceType::RT_Object) { + addObjectToNode(sceneNode, static_cast(it->second), c.mTransformation); + } + } +} + +void XmlSerializer::ReadObject(XmlNode &node) { + int id = IdNotSet, pid = IdNotSet, pindex = IdNotSet; + bool hasId = getNodeAttribute(node, XmlTag::id, id); + if (!hasId) { + return; + } + + bool hasPid = getNodeAttribute(node, XmlTag::pid, pid); + bool hasPindex = getNodeAttribute(node, XmlTag::pindex, pindex); + + Object *obj = new Object(id); + for (XmlNode ¤tNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == D3MF::XmlTag::mesh) { + auto mesh = ReadMesh(currentNode); + mesh->mName.Set(ai_to_string(id)); + + if (hasPid) { + auto it = mResourcesDictionnary.find(pid); + if (hasPindex && it != mResourcesDictionnary.end() && it->second->getType() == ResourceType::RT_BaseMaterials) { + BaseMaterials *materials = static_cast(it->second); + mesh->mMaterialIndex = materials->mMaterialIndex[pindex]; + } + } + + obj->mMeshes.push_back(mesh); + obj->mMeshIndex.push_back(mMeshCount); + mMeshCount++; + } else if (currentName == D3MF::XmlTag::components) { + for (XmlNode ¤tSubNode : currentNode.children()) { + const std::string subNodeName = currentSubNode.name(); + if (subNodeName == D3MF::XmlTag::component) { + int objectId = IdNotSet; + std::string componentTransformStr; + aiMatrix4x4 componentTransform; + if (getNodeAttribute(currentSubNode, D3MF::XmlTag::transform, componentTransformStr)) { + componentTransform = parseTransformMatrix(componentTransformStr); + } + + if (getNodeAttribute(currentSubNode, D3MF::XmlTag::objectid, objectId)) { + obj->mComponents.push_back({ objectId, componentTransform }); + } + } + } + } + } + + mResourcesDictionnary.insert(std::make_pair(id, obj)); +} + +aiMesh *XmlSerializer::ReadMesh(XmlNode &node) { + if (node.empty()) { + return nullptr; + } + + aiMesh *mesh = new aiMesh(); + for (XmlNode ¤tNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == XmlTag::vertices) { + ImportVertices(currentNode, mesh); + } else if (currentName == XmlTag::triangles) { + ImportTriangles(currentNode, mesh); + } + } + + return mesh; +} + +void XmlSerializer::ReadMetadata(XmlNode &node) { + pugi::xml_attribute attribute = node.attribute(D3MF::XmlTag::meta_name); + const std::string name = attribute.as_string(); + const std::string value = node.value(); + if (name.empty()) { + return; + } + + MetaEntry entry; + entry.name = name; + entry.value = value; + mMetaData.push_back(entry); +} + +void XmlSerializer::ImportVertices(XmlNode &node, aiMesh *mesh) { + ai_assert(nullptr != mesh); + + std::vector vertices; + for (XmlNode ¤tNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == XmlTag::vertex) { + vertices.push_back(ReadVertex(currentNode)); + } + } + + mesh->mNumVertices = static_cast(vertices.size()); + mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + std::copy(vertices.begin(), vertices.end(), mesh->mVertices); +} + +void XmlSerializer::ImportTriangles(XmlNode &node, aiMesh *mesh) { + std::vector faces; + for (XmlNode ¤tNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == XmlTag::triangle) { + int pid = IdNotSet, p1 = IdNotSet; + bool hasPid = getNodeAttribute(currentNode, D3MF::XmlTag::pid, pid); + bool hasP1 = getNodeAttribute(currentNode, D3MF::XmlTag::p1, p1); + + if (hasPid && hasP1) { + auto it = mResourcesDictionnary.find(pid); + if (it != mResourcesDictionnary.end()) { + if (it->second->getType() == ResourceType::RT_BaseMaterials) { + BaseMaterials *baseMaterials = static_cast(it->second); + mesh->mMaterialIndex = baseMaterials->mMaterialIndex[p1]; + } else if (it->second->getType() == ResourceType::RT_Texture2DGroup) { + if (mesh->mTextureCoords[0] == nullptr) { + Texture2DGroup *group = static_cast(it->second); + const std::string name = ai_to_string(group->mTexId); + for (size_t i = 0; i < mMaterials.size(); ++i) { + if (name == mMaterials[i]->GetName().C_Str()) { + mesh->mMaterialIndex = static_cast(i); + } + } + mesh->mTextureCoords[0] = new aiVector3D[group->mTex2dCoords.size()]; + for (unsigned int i = 0; i < group->mTex2dCoords.size(); ++i) { + mesh->mTextureCoords[0][i] = aiVector3D(group->mTex2dCoords[i].x, group->mTex2dCoords[i].y, 0); + } + } + } + } + } + + aiFace face = ReadTriangle(currentNode); + faces.push_back(face); + } + } + + mesh->mNumFaces = static_cast(faces.size()); + mesh->mFaces = new aiFace[mesh->mNumFaces]; + mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + std::copy(faces.begin(), faces.end(), mesh->mFaces); +} + +void XmlSerializer::ReadBaseMaterials(XmlNode &node) { + int id = IdNotSet; + if (getNodeAttribute(node, D3MF::XmlTag::id, id)) { + BaseMaterials *baseMaterials = new BaseMaterials(id); + + for (XmlNode ¤tNode : node.children()) { + const std::string currentName = currentNode.name(); + if (currentName == XmlTag::basematerials_base) { + baseMaterials->mMaterialIndex.push_back(static_cast(mMaterials.size())); + mMaterials.push_back(readMaterialDef(currentNode, id)); + } + } + + mResourcesDictionnary.insert(std::make_pair(id, baseMaterials)); + } +} + +void XmlSerializer::ReadEmbeddecTexture(XmlNode &node) { + if (node.empty()) { + return; + } + + std::string value; + EmbeddedTexture *tex2D = nullptr; + if (XmlParser::getStdStrAttribute(node, XmlTag::id, value)) { + tex2D = new EmbeddedTexture(atoi(value.c_str())); + } + if (nullptr == tex2D) { + return; + } + + if (XmlParser::getStdStrAttribute(node, XmlTag::path, value)) { + tex2D->mPath = value; + } + if (XmlParser::getStdStrAttribute(node, XmlTag::texture_content_type, value)) { + tex2D->mContentType = value; + } + if (XmlParser::getStdStrAttribute(node, XmlTag::texture_tilestyleu, value)) { + tex2D->mTilestyleU = value; + } + if (XmlParser::getStdStrAttribute(node, XmlTag::texture_tilestylev, value)) { + tex2D->mTilestyleV = value; + } + mEmbeddedTextures.emplace_back(tex2D); + StoreEmbeddedTexture(tex2D); +} + +void XmlSerializer::StoreEmbeddedTexture(EmbeddedTexture *tex) { + aiMaterial *mat = new aiMaterial; + aiString s; + s.Set(ai_to_string(tex->mId).c_str()); + mat->AddProperty(&s, AI_MATKEY_NAME); + const std::string name = "*" + tex->mPath; + s.Set(name); + mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(0)); + + aiColor3D col; + mat->AddProperty(&col, 1, AI_MATKEY_COLOR_DIFFUSE); + mat->AddProperty(&col, 1, AI_MATKEY_COLOR_AMBIENT); + mat->AddProperty(&col, 1, AI_MATKEY_COLOR_EMISSIVE); + mat->AddProperty(&col, 1, AI_MATKEY_COLOR_SPECULAR); + mMaterials.emplace_back(mat); +} + +void XmlSerializer::ReadTextureCoords2D(XmlNode &node, Texture2DGroup *tex2DGroup) { + if (node.empty() || nullptr == tex2DGroup) { + return; + } + + int id = IdNotSet; + if (XmlParser::getIntAttribute(node, "texid", id)) { + tex2DGroup->mTexId = id; + } + + double value = 0.0; + for (XmlNode currentNode : node.children()) { + const std::string currentName = currentNode.name(); + aiVector2D texCoord; + if (currentName == XmlTag::texture_2d_coord) { + XmlParser::getDoubleAttribute(currentNode, XmlTag::texture_cuurd_u, value); + texCoord.x = (ai_real)value; + XmlParser::getDoubleAttribute(currentNode, XmlTag::texture_cuurd_v, value); + texCoord.y = (ai_real)value; + tex2DGroup->mTex2dCoords.push_back(texCoord); + } + } +} + +void XmlSerializer::ReadTextureGroup(XmlNode &node) { + if (node.empty()) { + return; + } + + int id = IdNotSet; + if (!XmlParser::getIntAttribute(node, XmlTag::id, id)) { + return; + } + + Texture2DGroup *group = new Texture2DGroup(id); + ReadTextureCoords2D(node, group); + mResourcesDictionnary.insert(std::make_pair(id, group)); +} + +aiMaterial *XmlSerializer::readMaterialDef(XmlNode &node, unsigned int basematerialsId) { + aiMaterial *material = new aiMaterial(); + material->mNumProperties = 0; + std::string name; + bool hasName = getNodeAttribute(node, D3MF::XmlTag::basematerials_name, name); + + std::string stdMaterialName; + const std::string strId(ai_to_string(basematerialsId)); + stdMaterialName += "id"; + stdMaterialName += strId; + stdMaterialName += "_"; + if (hasName) { + stdMaterialName += std::string(name); + } else { + stdMaterialName += "basemat_"; + stdMaterialName += ai_to_string(mMaterials.size()); + } + + aiString assimpMaterialName(stdMaterialName); + material->AddProperty(&assimpMaterialName, AI_MATKEY_NAME); + + assignDiffuseColor(node, material); + + return material; +} + +void XmlSerializer::StoreMaterialsInScene(aiScene *scene) { + if (nullptr == scene || mMaterials.empty()) { + return; + } + + scene->mNumMaterials = static_cast(mMaterials.size()); + scene->mMaterials = new aiMaterial *[scene->mNumMaterials]; + for (size_t i = 0; i < mMaterials.size(); ++i) { + scene->mMaterials[i] = mMaterials[i]; + } +} + +} // namespace D3MF +} // namespace Assimp diff --git a/code/AssetLib/3MF/XmlSerializer.h b/code/AssetLib/3MF/XmlSerializer.h new file mode 100644 index 000000000..14da82e99 --- /dev/null +++ b/code/AssetLib/3MF/XmlSerializer.h @@ -0,0 +1,96 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2021, 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. + +---------------------------------------------------------------------- +*/ +#pragma once + +#include +#include +#include +#include + +struct aiNode; +struct aiMesh; +struct aiMaterial; + +namespace Assimp { +namespace D3MF { + +class Resource; +class D3MFOpcPackage; +class Object; +class Texture2DGroup; +class EmbeddedTexture; + +class XmlSerializer { +public: + XmlSerializer(XmlParser *xmlParser); + ~XmlSerializer(); + void ImportXml(aiScene *scene); + +private: + void addObjectToNode(aiNode *parent, Object *obj, aiMatrix4x4 nodeTransform); + void ReadObject(XmlNode &node); + aiMesh *ReadMesh(XmlNode &node); + void ReadMetadata(XmlNode &node); + void ImportVertices(XmlNode &node, aiMesh *mesh); + void ImportTriangles(XmlNode &node, aiMesh *mesh); + void ReadBaseMaterials(XmlNode &node); + void ReadEmbeddecTexture(XmlNode &node); + void StoreEmbeddedTexture(EmbeddedTexture *tex); + void ReadTextureCoords2D(XmlNode &node, Texture2DGroup *tex2DGroup); + void ReadTextureGroup(XmlNode &node); + aiMaterial *readMaterialDef(XmlNode &node, unsigned int basematerialsId); + void StoreMaterialsInScene(aiScene *scene); + +private: + struct MetaEntry { + std::string name; + std::string value; + }; + std::vector mMetaData; + std::vector mEmbeddedTextures; + std::vector mMaterials; + std::map mResourcesDictionnary; + unsigned int mMeshCount; + XmlParser *mXmlParser; +}; + +} // namespace D3MF +} // namespace Assimp diff --git a/code/AssetLib/AMF/AMFImporter.cpp b/code/AssetLib/AMF/AMFImporter.cpp index 615882b6a..88a38b827 100644 --- a/code/AssetLib/AMF/AMFImporter.cpp +++ b/code/AssetLib/AMF/AMFImporter.cpp @@ -303,7 +303,7 @@ void AMFImporter::ParseNode_Root() { } XmlNode node = *root; mUnit = ai_tolower(std::string(node.attribute("unit").as_string())); - + mVersion = node.attribute("version").as_string(); // Read attributes for node . diff --git a/code/AssetLib/AMF/AMFImporter_Geometry.cpp b/code/AssetLib/AMF/AMFImporter_Geometry.cpp index 1fd2c49a4..1d2a1f5b4 100644 --- a/code/AssetLib/AMF/AMFImporter_Geometry.cpp +++ b/code/AssetLib/AMF/AMFImporter_Geometry.cpp @@ -75,7 +75,7 @@ void AMFImporter::ParseNode_Mesh(XmlNode &node) { found_volumes = true; } ParseHelper_Node_Exit(); - } + } if (!found_verts && !found_volumes) { mNodeElement_Cur->Child.push_back(ne); @@ -199,9 +199,9 @@ void AMFImporter::ParseNode_Volume(XmlNode &node) { // Read attributes for node . // and assign read data - + ((AMFVolume *)ne)->MaterialID = node.attribute("materialid").as_string(); - + ((AMFVolume *)ne)->Type = type; // Check for child nodes bool col_read = false; diff --git a/code/AssetLib/AMF/AMFImporter_Postprocess.cpp b/code/AssetLib/AMF/AMFImporter_Postprocess.cpp index 43d0de52f..d56d6681d 100644 --- a/code/AssetLib/AMF/AMFImporter_Postprocess.cpp +++ b/code/AssetLib/AMF/AMFImporter_Postprocess.cpp @@ -69,7 +69,7 @@ aiColor4D AMFImporter::SPP_Material::GetColor(const float /*pX*/, const float /* } tcol = Color->Color; - + // Check if default color must be used if ((tcol.r == 0) && (tcol.g == 0) && (tcol.b == 0) && (tcol.a == 0)) { tcol.r = 0.5f; @@ -99,10 +99,10 @@ void AMFImporter::PostprocessHelper_CreateMeshDataArray(const AMFMesh &nodeEleme } // all coordinates stored as child and we need to reserve space for future push_back's. - vertexCoordinateArray.reserve(vn->Child.size()); + vertexCoordinateArray.reserve(vn->Child.size()); // colors count equal vertices count. - pVertexColorArray.resize(vn->Child.size()); + pVertexColorArray.resize(vn->Child.size()); col_idx = 0; // Inside vertices collect all data and place to arrays diff --git a/code/AssetLib/ASE/ASEParser.h b/code/AssetLib/ASE/ASEParser.h index d04fc0662..f49cfc36f 100644 --- a/code/AssetLib/ASE/ASEParser.h +++ b/code/AssetLib/ASE/ASEParser.h @@ -95,8 +95,8 @@ struct Material : public D3DS::Material { Material(Material &&other) AI_NO_EXCEPT : D3DS::Material(std::move(other)), avSubMaterials(std::move(other.avSubMaterials)), - pcInstance(std::move(other.pcInstance)), - bNeed(std::move(other.bNeed)) { + pcInstance(other.pcInstance), + bNeed(other.bNeed) { other.pcInstance = nullptr; } @@ -108,8 +108,8 @@ struct Material : public D3DS::Material { //D3DS::Material::operator=(std::move(other)); avSubMaterials = std::move(other.avSubMaterials); - pcInstance = std::move(other.pcInstance); - bNeed = std::move(other.bNeed); + pcInstance = other.pcInstance; + bNeed = other.bNeed; other.pcInstance = nullptr; diff --git a/code/AssetLib/Assbin/AssbinFileWriter.cpp b/code/AssetLib/Assbin/AssbinFileWriter.cpp index 95379a303..2519b0b93 100644 --- a/code/AssetLib/Assbin/AssbinFileWriter.cpp +++ b/code/AssetLib/Assbin/AssbinFileWriter.cpp @@ -172,7 +172,7 @@ inline size_t Write(IOStream *stream, const aiQuaternion &v) { t += Write(stream, v.z); ai_assert(t == 16); - return 16; + return t; } // ----------------------------------------------------------------------------------- diff --git a/code/AssetLib/Assjson/json_exporter.cpp b/code/AssetLib/Assjson/json_exporter.cpp index b9099d392..7b2c8ec81 100644 --- a/code/AssetLib/Assjson/json_exporter.cpp +++ b/code/AssetLib/Assjson/json_exporter.cpp @@ -41,12 +41,17 @@ public: enum { Flag_DoNotIndent = 0x1, Flag_WriteSpecialFloats = 0x2, + Flag_SkipWhitespaces = 0x4 }; - + JSONWriter(Assimp::IOStream &out, unsigned int flags = 0u) : - out(out), first(), flags(flags) { + out(out), indent (""), newline("\n"), space(" "), buff (), first(false), flags(flags) { // make sure that all formatting happens using the standard, C locale and not the user's current locale buff.imbue(std::locale("C")); + if (flags & Flag_SkipWhitespaces) { + newline = ""; + space = ""; + } } ~JSONWriter() { @@ -70,7 +75,7 @@ public: void Key(const std::string &name) { AddIndentation(); Delimit(); - buff << '\"' + name + "\": "; + buff << '\"' + name + "\":" << space; } template @@ -78,12 +83,12 @@ public: AddIndentation(); Delimit(); - LiteralToString(buff, name) << '\n'; + LiteralToString(buff, name) << newline; } template void SimpleValue(const Literal &s) { - LiteralToString(buff, s) << '\n'; + LiteralToString(buff, s) << newline; } void SimpleValue(const void *buffer, size_t len) { @@ -102,7 +107,7 @@ public: } } - buff << '\"' << cur_out << "\"\n"; + buff << '\"' << cur_out << "\"" << newline; delete[] cur_out; } @@ -115,7 +120,7 @@ public: } } first = true; - buff << "{\n"; + buff << "{" << newline; PushIndent(); } @@ -123,7 +128,7 @@ public: PopIndent(); AddIndentation(); first = false; - buff << "}\n"; + buff << "}" << newline; } void StartArray(bool is_element = false) { @@ -135,19 +140,19 @@ public: } } first = true; - buff << "[\n"; + buff << "[" << newline; PushIndent(); } void EndArray() { PopIndent(); AddIndentation(); - buff << "]\n"; + buff << "]" << newline; first = false; } void AddIndentation() { - if (!(flags & Flag_DoNotIndent)) { + if (!(flags & Flag_DoNotIndent) && !(flags & Flag_SkipWhitespaces)) { buff << indent; } } @@ -156,7 +161,7 @@ public: if (!first) { buff << ','; } else { - buff << ' '; + buff << space; first = false; } } @@ -227,7 +232,9 @@ private: private: Assimp::IOStream &out; - std::string indent, newline; + std::string indent; + std::string newline; + std::string space; std::stringstream buff; bool first; @@ -765,7 +772,7 @@ void Write(JSONWriter &out, const aiScene &ai) { out.EndObj(); } -void ExportAssimp2Json(const char *file, Assimp::IOSystem *io, const aiScene *scene, const Assimp::ExportProperties *) { +void ExportAssimp2Json(const char *file, Assimp::IOSystem *io, const aiScene *scene, const Assimp::ExportProperties *pProperties) { std::unique_ptr str(io->Open(file, "wt")); if (!str) { throw DeadlyExportError("could not open output file"); @@ -782,7 +789,12 @@ void ExportAssimp2Json(const char *file, Assimp::IOSystem *io, const aiScene *sc splitter.Execute(scenecopy_tmp); // XXX Flag_WriteSpecialFloats is turned on by default, right now we don't have a configuration interface for exporters - JSONWriter s(*str, JSONWriter::Flag_WriteSpecialFloats); + + unsigned int flags = JSONWriter::Flag_WriteSpecialFloats; + if (pProperties->GetPropertyBool("JSON_SKIP_WHITESPACES", false)) { + flags |= JSONWriter::Flag_SkipWhitespaces; + } + JSONWriter s(*str, flags); Write(s, *scenecopy_tmp); } catch (...) { diff --git a/code/AssetLib/Assjson/mesh_splitter.cpp b/code/AssetLib/Assjson/mesh_splitter.cpp index 24385f9a0..9301cc27e 100644 --- a/code/AssetLib/Assjson/mesh_splitter.cpp +++ b/code/AssetLib/Assjson/mesh_splitter.cpp @@ -110,7 +110,7 @@ void MeshSplitter :: SplitMesh(unsigned int a, aiMesh* in_mesh, std::vectormNumVertices / LIMIT) + 1; - // create a std::vector to remember which vertices have already + // create a std::vector to remember which vertices have already // been copied and to which position (i.e. output index) std::vector was_copied_to; was_copied_to.resize(in_mesh->mNumVertices,WAS_NOT_COPIED); @@ -125,7 +125,7 @@ void MeshSplitter :: SplitMesh(unsigned int a, aiMesh* in_mesh, std::vectormNumVertices = 0; out_mesh->mMaterialIndex = in_mesh->mMaterialIndex; @@ -179,7 +179,7 @@ void MeshSplitter :: SplitMesh(unsigned int a, aiMesh* in_mesh, std::vectormNumVertices + iNeed > out_vertex_index) { @@ -240,7 +240,7 @@ void MeshSplitter :: SplitMesh(unsigned int a, aiMesh* in_mesh, std::vectormTextureCoords[c][out_mesh->mNumVertices] = in_mesh->mTextureCoords[c][index]; } } - // vertex colors + // vertex colors for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS;++c) { if (in_mesh->HasVertexColors( c)) { out_mesh->mColors[c][out_mesh->mNumVertices] = in_mesh->mColors[c][index]; diff --git a/code/AssetLib/Assjson/mesh_splitter.h b/code/AssetLib/Assjson/mesh_splitter.h index 326f73b41..3bb26118a 100644 --- a/code/AssetLib/Assjson/mesh_splitter.h +++ b/code/AssetLib/Assjson/mesh_splitter.h @@ -22,13 +22,13 @@ struct aiNode; // --------------------------------------------------------------------------- /** Splits meshes of unique vertices into meshes with no more vertices than - * a given, configurable threshold value. + * a given, configurable threshold value. */ -class MeshSplitter +class MeshSplitter { public: - + void SetLimit(unsigned int l) { LIMIT = l; } diff --git a/code/AssetLib/Assxml/AssxmlExporter.cpp b/code/AssetLib/Assxml/AssxmlExporter.cpp index 847ba0d7e..d244813b1 100644 --- a/code/AssetLib/Assxml/AssxmlExporter.cpp +++ b/code/AssetLib/Assxml/AssxmlExporter.cpp @@ -50,7 +50,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -namespace Assimp { +namespace Assimp { void ExportSceneAssxml(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) { diff --git a/code/AssetLib/B3D/B3DImporter.cpp b/code/AssetLib/B3D/B3DImporter.cpp index 3d9a5075a..11f7bcd14 100644 --- a/code/AssetLib/B3D/B3DImporter.cpp +++ b/code/AssetLib/B3D/B3DImporter.cpp @@ -143,7 +143,7 @@ AI_WONT_RETURN void B3DImporter::Oops() { } // ------------------------------------------------------------------------------------------------ -AI_WONT_RETURN void B3DImporter::Fail(string str) { +AI_WONT_RETURN void B3DImporter::Fail(const string &str) { #ifdef DEBUG_B3D ASSIMP_LOG_ERROR("Error in B3D file data: ", str); #endif diff --git a/code/AssetLib/B3D/B3DImporter.h b/code/AssetLib/B3D/B3DImporter.h index e2a75abdf..a7ed65c3b 100644 --- a/code/AssetLib/B3D/B3DImporter.h +++ b/code/AssetLib/B3D/B3DImporter.h @@ -96,7 +96,7 @@ private: }; AI_WONT_RETURN void Oops() AI_WONT_RETURN_SUFFIX; - AI_WONT_RETURN void Fail( std::string str ) AI_WONT_RETURN_SUFFIX; + AI_WONT_RETURN void Fail(const std::string &str) AI_WONT_RETURN_SUFFIX; void ReadTEXS(); void ReadBRUS(); diff --git a/code/AssetLib/Blender/BlenderLoader.cpp b/code/AssetLib/Blender/BlenderLoader.cpp index 7cf4e070e..42a7a1723 100644 --- a/code/AssetLib/Blender/BlenderLoader.cpp +++ b/code/AssetLib/Blender/BlenderLoader.cpp @@ -679,7 +679,7 @@ void BlenderImporter::BuildMaterials(ConversionData &conv_data) { BuildDefaultMaterial(conv_data); - for (std::shared_ptr mat : conv_data.materials_raw) { + for (const std::shared_ptr &mat : conv_data.materials_raw) { // reset per material global counters for (size_t i = 0; i < sizeof(conv_data.next_texture) / sizeof(conv_data.next_texture[0]); ++i) { diff --git a/code/AssetLib/Blender/BlenderModifier.h b/code/AssetLib/Blender/BlenderModifier.h index daf120087..d2fea43c1 100644 --- a/code/AssetLib/Blender/BlenderModifier.h +++ b/code/AssetLib/Blender/BlenderModifier.h @@ -52,9 +52,9 @@ namespace Assimp { namespace Blender { // ------------------------------------------------------------------------------------------- -/** +/** * Dummy base class for all blender modifiers. Modifiers are reused between imports, so - * they should be stateless and not try to cache model data. + * they should be stateless and not try to cache model data. */ // ------------------------------------------------------------------------------------------- class BlenderModifier { @@ -67,7 +67,7 @@ public: } // -------------------- - /** + /** * Check if *this* modifier is active, given a ModifierData& block. */ virtual bool IsActive( const ModifierData& /*modin*/) { @@ -75,10 +75,10 @@ public: } // -------------------- - /** + /** * Apply the modifier to a given output node. The original data used * to construct the node is given as well. Not called unless IsActive() - * was called and gave positive response. + * was called and gave positive response. */ virtual void DoIt(aiNode& /*out*/, ConversionData& /*conv_data*/, @@ -92,8 +92,8 @@ public: }; // ------------------------------------------------------------------------------------------- -/** - * Manage all known modifiers and instance and apply them if necessary +/** + * Manage all known modifiers and instance and apply them if necessary */ // ------------------------------------------------------------------------------------------- class BlenderModifierShowcase { @@ -113,8 +113,8 @@ private: // MODIFIERS ///////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------- -/** - * Mirror modifier. Status: implemented. +/** + * Mirror modifier. Status: implemented. */ // ------------------------------------------------------------------------------------------- class BlenderModifier_Mirror : public BlenderModifier { diff --git a/code/AssetLib/C4D/C4DImporter.cpp b/code/AssetLib/C4D/C4DImporter.cpp index 434d1429e..14a94958b 100644 --- a/code/AssetLib/C4D/C4DImporter.cpp +++ b/code/AssetLib/C4D/C4DImporter.cpp @@ -146,8 +146,14 @@ void C4DImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS ThrowException("failed to read document " + pFile); } + // Generate the root-node pScene->mRootNode = new aiNode(""); + // convert left-handed to right-handed + pScene->mRootNode->mTransformation.a1 = 0.01f; + pScene->mRootNode->mTransformation.b2 = 0.01f; + pScene->mRootNode->mTransformation.c3 = -0.01f; + // first convert all materials ReadMaterials(doc->GetFirstMaterial()); diff --git a/code/AssetLib/COB/COBLoader.cpp b/code/AssetLib/COB/COBLoader.cpp index 94327c683..3bef260e5 100644 --- a/code/AssetLib/COB/COBLoader.cpp +++ b/code/AssetLib/COB/COBLoader.cpp @@ -230,7 +230,7 @@ void COBImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy } // ------------------------------------------------------------------------------------------------ -void ConvertTexture(std::shared_ptr tex, aiMaterial *out, aiTextureType type) { +void ConvertTexture(const std::shared_ptr &tex, aiMaterial *out, aiTextureType type) { const aiString path(tex->path); out->AddProperty(&path, AI_MATKEY_TEXTURE(type, 0)); out->AddProperty(&tex->transform, 1, AI_MATKEY_UVTRANSFORM(type, 0)); @@ -884,7 +884,7 @@ void COBImporter::ReadBinaryFile(Scene &out, StreamReaderLE *reader) { std::string type; type += reader->GetI1(); type += reader->GetI1(); - type += reader->GetI1(); + type += reader->GetI1(); type += reader->GetI1(); ChunkInfo nfo; diff --git a/code/AssetLib/COB/COBLoader.h b/code/AssetLib/COB/COBLoader.h index 2317d094e..e4d41e500 100644 --- a/code/AssetLib/COB/COBLoader.h +++ b/code/AssetLib/COB/COBLoader.h @@ -77,7 +77,7 @@ class COBImporter : public BaseImporter public: COBImporter(); ~COBImporter(); - + // -------------------- bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const; diff --git a/code/AssetLib/Collada/ColladaLoader.cpp b/code/AssetLib/Collada/ColladaLoader.cpp index 492e6971f..f7b5f2278 100644 --- a/code/AssetLib/Collada/ColladaLoader.cpp +++ b/code/AssetLib/Collada/ColladaLoader.cpp @@ -135,14 +135,15 @@ bool ColladaLoader::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool // XML - too generic, we need to open the file and search for typical keywords if (extension == "xml" || !extension.length() || checkSig) { - /* If CanRead() is called in order to check whether we - * support a specific file extension in general pIOHandler - * might be nullptr and it's our duty to return true here. - */ - if (!pIOHandler) { + // If CanRead() is called in order to check whether we + // support a specific file extension in general pIOHandler + // might be nullptr and it's our duty to return true here. + if (nullptr == pIOHandler) { return true; } - static const char *tokens[] = { "mNumMeshes = static_cast(newMeshRefs.size()); - if (newMeshRefs.size()) { + if (!newMeshRefs.empty()) { struct UIntTypeConverter { unsigned int operator()(const size_t &v) const { return static_cast(v); @@ -619,6 +620,10 @@ aiMesh *ColladaLoader::CreateMesh(const ColladaParser &pParser, const Mesh *pSrc dstMesh->mName = pSrcMesh->mId; } + if (pSrcMesh->mPositions.empty()) { + return dstMesh.release(); + } + // count the vertices addressed by its faces const size_t numVertices = std::accumulate(pSrcMesh->mFaceSize.begin() + pStartFace, pSrcMesh->mFaceSize.begin() + pStartFace + pSubMesh.mNumFaces, size_t(0)); @@ -1540,7 +1545,7 @@ void ColladaLoader::AddTexture(aiMaterial &mat, map = -1; for (std::string::const_iterator it = sampler.mUVChannel.begin(); it != sampler.mUVChannel.end(); ++it) { if (IsNumeric(*it)) { - map = strtoul10(&(*it)); + map = strtoul10(&(*it)); break; } } @@ -1671,7 +1676,7 @@ void ColladaLoader::BuildMaterials(ColladaParser &pParser, aiScene * /*pScene*/) const Material &material = matIt->second; // a material is only a reference to an effect ColladaParser::EffectLibrary::iterator effIt = pParser.mEffectLibrary.find(material.mEffect); - if (effIt == pParser.mEffectLibrary.end()) + if (effIt == pParser.mEffectLibrary.end()) continue; Effect &effect = effIt->second; @@ -1682,7 +1687,7 @@ void ColladaLoader::BuildMaterials(ColladaParser &pParser, aiScene * /*pScene*/) // store the material mMaterialIndexByName[matIt->first] = newMats.size(); - newMats.push_back(std::pair(&effect, mat)); + newMats.emplace_back(&effect, mat); } // ScenePreprocessor generates a default material automatically if none is there. // All further code here in this loader works well without a valid material so diff --git a/code/AssetLib/Collada/ColladaParser.cpp b/code/AssetLib/Collada/ColladaParser.cpp index 5dbf0a567..94b9b18ed 100644 --- a/code/AssetLib/Collada/ColladaParser.cpp +++ b/code/AssetLib/Collada/ColladaParser.cpp @@ -170,10 +170,10 @@ ColladaParser::ColladaParser(IOSystem *pIOHandler, const std::string &pFile) : // ------------------------------------------------------------------------------------------------ // Destructor, private as well ColladaParser::~ColladaParser() { - for (auto & it : mNodeLibrary) { + for (auto &it : mNodeLibrary) { delete it.second; } - for (auto & it : mMeshLibrary) { + for (auto &it : mMeshLibrary) { delete it.second; } } @@ -231,11 +231,7 @@ void ColladaParser::UriDecodePath(aiString &ss) { // Maxon Cinema Collada Export writes "file:///C:\andsoon" with three slashes... // I need to filter it without destroying linux paths starting with "/somewhere" -#if defined(_MSC_VER) if (ss.data[0] == '/' && isalpha((unsigned char)ss.data[1]) && ss.data[2] == ':') { -#else - if (ss.data[0] == '/' && isalpha((unsigned char)ss.data[1]) && ss.data[2] == ':') { -#endif --ss.length; ::memmove(ss.data, ss.data + 1, ss.length); ss.data[ss.length] = 0; @@ -396,7 +392,7 @@ void ColladaParser::ReadAnimationClipLibrary(XmlNode &node) { std::string animName; if (!XmlParser::getStdStrAttribute(node, "name", animName)) { - if (!XmlParser::getStdStrAttribute( node, "id", animName )) { + if (!XmlParser::getStdStrAttribute(node, "id", animName)) { animName = std::string("animation_") + ai_to_string(mAnimationClipLibrary.size()); } } @@ -420,7 +416,7 @@ void ColladaParser::ReadAnimationClipLibrary(XmlNode &node) { void ColladaParser::PostProcessControllers() { std::string meshId; - for (auto & it : mControllerLibrary) { + for (auto &it : mControllerLibrary) { meshId = it.second.mMeshId; if (meshId.empty()) { continue; @@ -445,7 +441,7 @@ void ColladaParser::PostProcessRootAnimations() { } Animation temp; - for (auto & it : mAnimationClipLibrary) { + for (auto &it : mAnimationClipLibrary) { std::string clipName = it.first; Animation *clip = new Animation(); @@ -453,7 +449,7 @@ void ColladaParser::PostProcessRootAnimations() { temp.mSubAnims.push_back(clip); - for (std::string animationID : it.second) { + for (const std::string &animationID : it.second) { AnimationLibrary::iterator animation = mAnimationLibrary.find(animationID); if (animation != mAnimationLibrary.end()) { @@ -529,7 +525,7 @@ void ColladaParser::ReadAnimation(XmlNode &node, Collada::Animation *pParent) { // have it read into a channel ChannelMap::iterator newChannel = channels.insert(std::make_pair(id, AnimationChannel())).first; ReadAnimationSampler(currentNode, newChannel->second); - } + } } else if (currentName == "channel") { std::string source_name, target; XmlParser::getStdStrAttribute(currentNode, "source", source_name); @@ -552,7 +548,7 @@ void ColladaParser::ReadAnimation(XmlNode &node, Collada::Animation *pParent) { pParent->mSubAnims.push_back(anim); } - for (const auto & channel : channels) { + for (const auto &channel : channels) { anim->mChannels.push_back(channel.second); } @@ -626,8 +622,6 @@ void ColladaParser::ReadController(XmlNode &node, Collada::Controller &controlle XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode); XmlNode currentNode; while (xmlIt.getNext(currentNode)) { - - //for (XmlNode ¤tNode : node.children()) { const std::string ¤tName = currentNode.name(); if (currentName == "morph") { controller.mType = Morph; @@ -644,7 +638,7 @@ void ColladaParser::ReadController(XmlNode &node, Collada::Controller &controlle } else if (currentName == "skin") { std::string id; if (XmlParser::getStdStrAttribute(currentNode, "source", id)) { - controller.mMeshId = id.substr(1, id.size()-1); + controller.mMeshId = id.substr(1, id.size() - 1); } } else if (currentName == "bind_shape_matrix") { std::string v; @@ -698,7 +692,7 @@ void ColladaParser::ReadControllerJoints(XmlNode &node, Collada::Controller &pCo } else if (strcmp(attrSemantic, "INV_BIND_MATRIX") == 0) { pController.mJointOffsetMatrixSource = attrSource; } else { - throw DeadlyImportError("Unknown semantic \"" , attrSemantic , "\" in data element"); + throw DeadlyImportError("Unknown semantic \"", attrSemantic, "\" in data element"); } } } @@ -708,7 +702,7 @@ void ColladaParser::ReadControllerJoints(XmlNode &node, Collada::Controller &pCo // Reads the joint weights for the given controller void ColladaParser::ReadControllerWeights(XmlNode &node, Collada::Controller &pController) { // Read vertex count from attributes and resize the array accordingly - int vertexCount=0; + int vertexCount = 0; XmlParser::getIntAttribute(node, "count", vertexCount); pController.mWeightCounts.resize(vertexCount); @@ -723,7 +717,7 @@ void ColladaParser::ReadControllerWeights(XmlNode &node, Collada::Controller &pC // local URLS always start with a '#'. We don't support global URLs if (attrSource[0] != '#') { - throw DeadlyImportError( "Unsupported URL format in \"", attrSource, "\" in source attribute of data element"); + throw DeadlyImportError("Unsupported URL format in \"", attrSource, "\" in source attribute of data element"); } channel.mAccessor = attrSource + 1; @@ -777,7 +771,7 @@ void ColladaParser::ReadImageLibrary(XmlNode &node) { const std::string ¤tName = currentNode.name(); if (currentName == "image") { std::string id; - if (XmlParser::getStdStrAttribute( currentNode, "id", id )) { + if (XmlParser::getStdStrAttribute(currentNode, "id", id)) { mImageLibrary[id] = Image(); // read on from there ReadImage(currentNode, mImageLibrary[id]); @@ -907,7 +901,7 @@ void ColladaParser::ReadCameraLibrary(XmlNode &node) { if (!name.empty()) { cam.mName = name; } - ReadCamera(currentNode, cam); + ReadCamera(currentNode, cam); } } } @@ -920,7 +914,7 @@ void ColladaParser::ReadMaterial(XmlNode &node, Collada::Material &pMaterial) { if (currentName == "instance_effect") { std::string url; readUrlAttribute(currentNode, url); - pMaterial.mEffect = url.c_str(); + pMaterial.mEffect = url; } } } @@ -1361,8 +1355,8 @@ void ColladaParser::ReadMesh(XmlNode &node, Mesh &pMesh) { } else if (currentName == "vertices") { ReadVertexData(currentNode, pMesh); } else if (currentName == "triangles" || currentName == "lines" || currentName == "linestrips" || - currentName == "polygons" || currentName == "polylist" || currentName == "trifans" || - currentName == "tristrips") { + currentName == "polygons" || currentName == "polylist" || currentName == "trifans" || + currentName == "tristrips") { ReadIndexData(currentNode, pMesh); } } @@ -1439,9 +1433,8 @@ void ColladaParser::ReadDataArray(XmlNode &node) { throw DeadlyImportError("Expected more values while reading float_array contents."); } - ai_real value; // read a number - //SkipSpacesAndLineEnd(&content); + ai_real value; content = fast_atoreal_move(content, value); data.mValues.push_back(value); // skip whitespace after it @@ -1489,11 +1482,10 @@ void ColladaParser::ReadAccessor(XmlNode &node, const std::string &pID) { std::string name; if (XmlParser::hasAttribute(currentNode, "name")) { XmlParser::getStdStrAttribute(currentNode, "name", name); - //name = mReader->getAttributeValue(attrName); // analyse for common type components and store it's sub-offset in the corresponding field - /* Cartesian coordinates */ + // Cartesian coordinates if (name == "X") acc.mSubOffset[0] = acc.mParams.size(); else if (name == "Y") @@ -1674,12 +1666,9 @@ void ColladaParser::ReadInputChannel(XmlNode &node, std::vector &p // read set if texture coordinates if (channel.mType == IT_Texcoord || channel.mType == IT_Color) { - int attrSet = -1; - if (XmlParser::hasAttribute(node, "set")) { - XmlParser::getIntAttribute(node, "set", attrSet); - } - - channel.mIndex = attrSet; + unsigned int attrSet = 0; + if (XmlParser::getUIntAttribute(node, "set", attrSet)) + channel.mIndex = attrSet; } // store, if valid type @@ -1704,20 +1693,20 @@ size_t ColladaParser::ReadPrimitives(XmlNode &node, Mesh &pMesh, std::vector 0) { + if (pNumPrimitives > 0) { std::string v; XmlParser::getValueAsString(node, v); const char *content = v.c_str(); @@ -1925,87 +1914,87 @@ void ColladaParser::ExtractDataObjectFromChannel(const InputChannel &pInput, siz // now we reinterpret it according to the type we're reading here switch (pInput.mType) { - case IT_Position: // ignore all position streams except 0 - there can be only one position - if (pInput.mIndex == 0) { - pMesh.mPositions.push_back(aiVector3D(obj[0], obj[1], obj[2])); - } else { - ASSIMP_LOG_ERROR("Collada: just one vertex position stream supported"); - } - break; - case IT_Normal: + case IT_Position: // ignore all position streams except 0 - there can be only one position + if (pInput.mIndex == 0) { + pMesh.mPositions.push_back(aiVector3D(obj[0], obj[1], obj[2])); + } else { + ASSIMP_LOG_ERROR("Collada: just one vertex position stream supported"); + } + break; + case IT_Normal: + // pad to current vertex count if necessary + if (pMesh.mNormals.size() < pMesh.mPositions.size() - 1) + pMesh.mNormals.insert(pMesh.mNormals.end(), pMesh.mPositions.size() - pMesh.mNormals.size() - 1, aiVector3D(0, 1, 0)); + + // ignore all normal streams except 0 - there can be only one normal + if (pInput.mIndex == 0) { + pMesh.mNormals.push_back(aiVector3D(obj[0], obj[1], obj[2])); + } else { + ASSIMP_LOG_ERROR("Collada: just one vertex normal stream supported"); + } + break; + case IT_Tangent: + // pad to current vertex count if necessary + if (pMesh.mTangents.size() < pMesh.mPositions.size() - 1) + pMesh.mTangents.insert(pMesh.mTangents.end(), pMesh.mPositions.size() - pMesh.mTangents.size() - 1, aiVector3D(1, 0, 0)); + + // ignore all tangent streams except 0 - there can be only one tangent + if (pInput.mIndex == 0) { + pMesh.mTangents.push_back(aiVector3D(obj[0], obj[1], obj[2])); + } else { + ASSIMP_LOG_ERROR("Collada: just one vertex tangent stream supported"); + } + break; + case IT_Bitangent: + // pad to current vertex count if necessary + if (pMesh.mBitangents.size() < pMesh.mPositions.size() - 1) { + pMesh.mBitangents.insert(pMesh.mBitangents.end(), pMesh.mPositions.size() - pMesh.mBitangents.size() - 1, aiVector3D(0, 0, 1)); + } + + // ignore all bitangent streams except 0 - there can be only one bitangent + if (pInput.mIndex == 0) { + pMesh.mBitangents.push_back(aiVector3D(obj[0], obj[1], obj[2])); + } else { + ASSIMP_LOG_ERROR("Collada: just one vertex bitangent stream supported"); + } + break; + case IT_Texcoord: + // up to 4 texture coord sets are fine, ignore the others + if (pInput.mIndex < AI_MAX_NUMBER_OF_TEXTURECOORDS) { // pad to current vertex count if necessary - if (pMesh.mNormals.size() < pMesh.mPositions.size() - 1) - pMesh.mNormals.insert(pMesh.mNormals.end(), pMesh.mPositions.size() - pMesh.mNormals.size() - 1, aiVector3D(0, 1, 0)); + if (pMesh.mTexCoords[pInput.mIndex].size() < pMesh.mPositions.size() - 1) + pMesh.mTexCoords[pInput.mIndex].insert(pMesh.mTexCoords[pInput.mIndex].end(), + pMesh.mPositions.size() - pMesh.mTexCoords[pInput.mIndex].size() - 1, aiVector3D(0, 0, 0)); - // ignore all normal streams except 0 - there can be only one normal - if (pInput.mIndex == 0) { - pMesh.mNormals.push_back(aiVector3D(obj[0], obj[1], obj[2])); - } else { - ASSIMP_LOG_ERROR("Collada: just one vertex normal stream supported"); + pMesh.mTexCoords[pInput.mIndex].push_back(aiVector3D(obj[0], obj[1], obj[2])); + if (0 != acc.mSubOffset[2] || 0 != acc.mSubOffset[3]) { + pMesh.mNumUVComponents[pInput.mIndex] = 3; } - break; - case IT_Tangent: + } else { + ASSIMP_LOG_ERROR("Collada: too many texture coordinate sets. Skipping."); + } + break; + case IT_Color: + // up to 4 color sets are fine, ignore the others + if (pInput.mIndex < AI_MAX_NUMBER_OF_COLOR_SETS) { // pad to current vertex count if necessary - if (pMesh.mTangents.size() < pMesh.mPositions.size() - 1) - pMesh.mTangents.insert(pMesh.mTangents.end(), pMesh.mPositions.size() - pMesh.mTangents.size() - 1, aiVector3D(1, 0, 0)); + if (pMesh.mColors[pInput.mIndex].size() < pMesh.mPositions.size() - 1) + pMesh.mColors[pInput.mIndex].insert(pMesh.mColors[pInput.mIndex].end(), + pMesh.mPositions.size() - pMesh.mColors[pInput.mIndex].size() - 1, aiColor4D(0, 0, 0, 1)); - // ignore all tangent streams except 0 - there can be only one tangent - if (pInput.mIndex == 0) { - pMesh.mTangents.push_back(aiVector3D(obj[0], obj[1], obj[2])); - } else { - ASSIMP_LOG_ERROR("Collada: just one vertex tangent stream supported"); - } - break; - case IT_Bitangent: - // pad to current vertex count if necessary - if (pMesh.mBitangents.size() < pMesh.mPositions.size() - 1) { - pMesh.mBitangents.insert(pMesh.mBitangents.end(), pMesh.mPositions.size() - pMesh.mBitangents.size() - 1, aiVector3D(0, 0, 1)); + aiColor4D result(0, 0, 0, 1); + for (size_t i = 0; i < pInput.mResolved->mSize; ++i) { + result[static_cast(i)] = obj[pInput.mResolved->mSubOffset[i]]; } + pMesh.mColors[pInput.mIndex].push_back(result); + } else { + ASSIMP_LOG_ERROR("Collada: too many vertex color sets. Skipping."); + } - // ignore all bitangent streams except 0 - there can be only one bitangent - if (pInput.mIndex == 0) { - pMesh.mBitangents.push_back(aiVector3D(obj[0], obj[1], obj[2])); - } else { - ASSIMP_LOG_ERROR("Collada: just one vertex bitangent stream supported"); - } - break; - case IT_Texcoord: - // up to 4 texture coord sets are fine, ignore the others - if (pInput.mIndex < AI_MAX_NUMBER_OF_TEXTURECOORDS) { - // pad to current vertex count if necessary - if (pMesh.mTexCoords[pInput.mIndex].size() < pMesh.mPositions.size() - 1) - pMesh.mTexCoords[pInput.mIndex].insert(pMesh.mTexCoords[pInput.mIndex].end(), - pMesh.mPositions.size() - pMesh.mTexCoords[pInput.mIndex].size() - 1, aiVector3D(0, 0, 0)); - - pMesh.mTexCoords[pInput.mIndex].push_back(aiVector3D(obj[0], obj[1], obj[2])); - if (0 != acc.mSubOffset[2] || 0 != acc.mSubOffset[3]) { - pMesh.mNumUVComponents[pInput.mIndex] = 3; - } - } else { - ASSIMP_LOG_ERROR("Collada: too many texture coordinate sets. Skipping."); - } - break; - case IT_Color: - // up to 4 color sets are fine, ignore the others - if (pInput.mIndex < AI_MAX_NUMBER_OF_COLOR_SETS) { - // pad to current vertex count if necessary - if (pMesh.mColors[pInput.mIndex].size() < pMesh.mPositions.size() - 1) - pMesh.mColors[pInput.mIndex].insert(pMesh.mColors[pInput.mIndex].end(), - pMesh.mPositions.size() - pMesh.mColors[pInput.mIndex].size() - 1, aiColor4D(0, 0, 0, 1)); - - aiColor4D result(0, 0, 0, 1); - for (size_t i = 0; i < pInput.mResolved->mSize; ++i) { - result[static_cast(i)] = obj[pInput.mResolved->mSubOffset[i]]; - } - pMesh.mColors[pInput.mIndex].push_back(result); - } else { - ASSIMP_LOG_ERROR("Collada: too many vertex color sets. Skipping."); - } - - break; - default: - // IT_Invalid and IT_Vertex - ai_assert(false && "shouldn't ever get here"); + break; + default: + // IT_Invalid and IT_Vertex + ai_assert(false && "shouldn't ever get here"); } } @@ -2170,10 +2159,10 @@ void ColladaParser::ReadNodeTransformation(XmlNode &node, Node *pNode, Transform // read as many parameters and store in the transformation for (unsigned int a = 0; a < sNumParameters[pType]; a++) { + // skip whitespace before the number + SkipSpacesAndLineEnd(&content); // read a number content = fast_atoreal_move(content, tf.f[a]); - // skip whitespace after it - SkipSpacesAndLineEnd(&content); } // place the transformation at the queue of the node @@ -2215,8 +2204,8 @@ void ColladaParser::ReadMaterialVertexInputBinding(XmlNode &node, Collada::Seman void ColladaParser::ReadEmbeddedTextures(ZipArchiveIOSystem &zip_archive) { // Attempt to load any undefined Collada::Image in ImageLibrary - for (ImageLibrary::iterator it = mImageLibrary.begin(); it != mImageLibrary.end(); ++it) { - Collada::Image &image = (*it).second; + for (auto & it : mImageLibrary) { + Collada::Image &image = it.second; if (image.mImageData.empty()) { std::unique_ptr image_file(zip_archive.Open(image.mFileName.c_str())); diff --git a/code/AssetLib/DXF/DXFLoader.cpp b/code/AssetLib/DXF/DXFLoader.cpp index 5d32ed121..2e38ed976 100644 --- a/code/AssetLib/DXF/DXFLoader.cpp +++ b/code/AssetLib/DXF/DXFLoader.cpp @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2021, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -549,7 +547,7 @@ void DXFImporter::ParseEntities(DXF::LineReader& reader, DXF::FileData& output) ++reader; } - ASSIMP_LOG_VERBOSE_DEBUG( "DXF: got ", block.lines.size()," polylines and ", block.insertions.size(), + ASSIMP_LOG_VERBOSE_DEBUG( "DXF: got ", block.lines.size()," polylines and ", block.insertions.size(), " inserted blocks in ENTITIES" ); } diff --git a/code/AssetLib/DXF/DXFLoader.h b/code/AssetLib/DXF/DXFLoader.h index 5319d2528..6649deb34 100644 --- a/code/AssetLib/DXF/DXFLoader.h +++ b/code/AssetLib/DXF/DXFLoader.h @@ -63,7 +63,7 @@ namespace DXF { } // --------------------------------------------------------------------------- -/** +/** * @brief DXF importer implementation. */ class DXFImporter : public BaseImporter { diff --git a/code/AssetLib/FBX/FBXConverter.cpp b/code/AssetLib/FBX/FBXConverter.cpp index a564b3e9b..fa7ee3986 100644 --- a/code/AssetLib/FBX/FBXConverter.cpp +++ b/code/AssetLib/FBX/FBXConverter.cpp @@ -862,7 +862,7 @@ bool FBXConverter::GenerateTransformationNodeChain(const Model &model, const std output_nodes.push_back(std::move(nd)); return false; } - + void FBXConverter::SetupNodeMetadata(const Model &model, aiNode &nd) { const PropertyTable &props = model.Props(); DirectPropertyMap unparsedProperties = props.GetUnparsedProperties(); @@ -917,8 +917,10 @@ void FBXConverter::ConvertModel(const Model &model, aiNode *parent, aiNode *root } else if (line) { const std::vector &indices = ConvertLine(*line, root_node); std::copy(indices.begin(), indices.end(), std::back_inserter(meshes)); - } else { + } else if (geo) { FBXImporter::LogWarn("ignoring unrecognized geometry: ", geo->Name()); + } else { + FBXImporter::LogWarn("skipping null geometry"); } } @@ -1766,6 +1768,7 @@ void FBXConverter::TrySetTextureProperties(aiMaterial *out_mat, const TextureMap // XXX handle all kinds of UV transformations uvTrafo.mScaling = tex->UVScaling(); uvTrafo.mTranslation = tex->UVTranslation(); + uvTrafo.mRotation = tex->UVRotation(); out_mat->AddProperty(&uvTrafo, 1, _AI_MATKEY_UVTRANSFORM_BASE, target, 0); const PropertyTable &props = tex->Props(); @@ -1885,6 +1888,7 @@ void FBXConverter::TrySetTextureProperties(aiMaterial *out_mat, const LayeredTex // XXX handle all kinds of UV transformations uvTrafo.mScaling = tex->UVScaling(); uvTrafo.mTranslation = tex->UVTranslation(); + uvTrafo.mRotation = tex->UVRotation(); out_mat->AddProperty(&uvTrafo, 1, _AI_MATKEY_UVTRANSFORM_BASE, target, texIndex); const PropertyTable &props = tex->Props(); @@ -2129,7 +2133,7 @@ void FBXConverter::SetShadingPropertiesCommon(aiMaterial *out_mat, const Propert if (ok) { out_mat->AddProperty(&Emissive, 1, AI_MATKEY_COLOR_EMISSIVE); } else { - const aiColor3D &emissiveColor = GetColorPropertyFromMaterial(props, "Maya|emissive", ok); + const aiColor3D &emissiveColor = GetColorProperty(props, "Maya|emissive", ok); if (ok) { out_mat->AddProperty(&emissiveColor, 1, AI_MATKEY_COLOR_EMISSIVE); } @@ -2216,7 +2220,7 @@ void FBXConverter::SetShadingPropertiesCommon(aiMaterial *out_mat, const Propert } // PBR material information - const aiColor3D &baseColor = GetColorPropertyFromMaterial(props, "Maya|base_color", ok); + const aiColor3D &baseColor = GetColorProperty(props, "Maya|base_color", ok); if (ok) { out_mat->AddProperty(&baseColor, 1, AI_MATKEY_BASE_COLOR); } @@ -2324,6 +2328,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial *out_mat, const PropertyTa // XXX handle all kinds of UV transformations uvTrafo.mScaling = tex->UVScaling(); uvTrafo.mTranslation = tex->UVTranslation(); + uvTrafo.mRotation = tex->UVRotation(); out_mat->AddProperty(&uvTrafo, 1, (name + "|uvtrafo").c_str(), aiTextureType_UNKNOWN, 0); int uvIndex = 0; @@ -2599,7 +2604,7 @@ void FBXConverter::ConvertAnimationStack(const AnimationStack &st) { anim->mMorphMeshChannels = new aiMeshMorphAnim *[numMorphMeshChannels]; anim->mNumMorphMeshChannels = numMorphMeshChannels; unsigned int i = 0; - for (auto morphAnimIt : morphAnimDatas) { + for (const auto &morphAnimIt : morphAnimDatas) { morphAnimData *animData = morphAnimIt.second; unsigned int numKeys = static_cast(animData->size()); aiMeshMorphAnim *meshMorphAnim = new aiMeshMorphAnim(); @@ -3569,7 +3574,7 @@ void FBXConverter::ConvertOrphanedEmbeddedTextures() { if (texture->Media() && texture->Media()->ContentLength() > 0) { realTexture = texture; } - } + } } } catch (...) { // do nothing diff --git a/code/AssetLib/FBX/FBXConverter.h b/code/AssetLib/FBX/FBXConverter.h index d208ab429..b9a494695 100644 --- a/code/AssetLib/FBX/FBXConverter.h +++ b/code/AssetLib/FBX/FBXConverter.h @@ -76,7 +76,7 @@ namespace Assimp { namespace FBX { class Document; -/** +/** * Convert a FBX #Document to #aiScene * @param out Empty scene to be populated * @param doc Parsed FBX document @@ -182,7 +182,7 @@ private: // ------------------------------------------------------------------------------------------------ void ConvertModel(const Model &model, aiNode *parent, aiNode *root_node, const aiMatrix4x4 &absolute_transform); - + // ------------------------------------------------------------------------------------------------ // MeshGeometry -> aiMesh, return mesh index + 1 or 0 if the conversion failed std::vector diff --git a/code/AssetLib/FBX/FBXDocument.cpp b/code/AssetLib/FBX/FBXDocument.cpp index 7adaadf6c..8e0439e18 100644 --- a/code/AssetLib/FBX/FBXDocument.cpp +++ b/code/AssetLib/FBX/FBXDocument.cpp @@ -57,9 +57,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -#include #include #include +#include +#include namespace Assimp { namespace FBX { @@ -248,10 +249,8 @@ Object::~Object() } // ------------------------------------------------------------------------------------------------ -FileGlobalSettings::FileGlobalSettings(const Document& doc, std::shared_ptr props) -: props(props) -, doc(doc) -{ +FileGlobalSettings::FileGlobalSettings(const Document &doc, std::shared_ptr props) : + props(std::move(props)), doc(doc) { // empty } @@ -636,7 +635,7 @@ std::vector Document::GetConnectionsBySourceSequenced(uint64_ } // ------------------------------------------------------------------------------------------------ -std::vector Document::GetConnectionsBySourceSequenced(uint64_t source, +std::vector Document::GetConnectionsBySourceSequenced(uint64_t source, const char* const* classnames, size_t count) const { return GetConnectionsSequenced(source, true, ConnectionsBySource(),classnames, count); diff --git a/code/AssetLib/FBX/FBXDocument.h b/code/AssetLib/FBX/FBXDocument.h index 69cda1c1a..1ee526368 100644 --- a/code/AssetLib/FBX/FBXDocument.h +++ b/code/AssetLib/FBX/FBXDocument.h @@ -500,6 +500,10 @@ public: return uvScaling; } + const ai_real &UVRotation() const { + return uvRotation; + } + const PropertyTable& Props() const { ai_assert(props.get()); return *props.get(); @@ -517,6 +521,7 @@ public: private: aiVector2D uvTrans; aiVector2D uvScaling; + ai_real uvRotation; std::string type; std::string relativeFileName; diff --git a/code/AssetLib/FBX/FBXExportNode.cpp b/code/AssetLib/FBX/FBXExportNode.cpp index 91e421420..817308ec8 100644 --- a/code/AssetLib/FBX/FBXExportNode.cpp +++ b/code/AssetLib/FBX/FBXExportNode.cpp @@ -144,9 +144,8 @@ void FBX::Node::AddP70time( // public member functions for writing nodes to stream void FBX::Node::Dump( - std::shared_ptr outfile, - bool binary, int indent -) { + const std::shared_ptr &outfile, + bool binary, int indent) { if (binary) { Assimp::StreamWriterLE outstream(outfile); DumpBinary(outstream); diff --git a/code/AssetLib/FBX/FBXExportNode.h b/code/AssetLib/FBX/FBXExportNode.h index c5f29ef0f..3aca98939 100644 --- a/code/AssetLib/FBX/FBXExportNode.h +++ b/code/AssetLib/FBX/FBXExportNode.h @@ -60,7 +60,7 @@ namespace FBX { } class FBX::Node { -public: +public: // TODO: accessors std::string name; // node name std::vector properties; // node properties @@ -157,9 +157,8 @@ public: // member functions for writing data to a file or stream // write the full node to the given file or stream void Dump( - std::shared_ptr outfile, - bool binary, int indent - ); + const std::shared_ptr &outfile, + bool binary, int indent); void Dump(Assimp::StreamWriterLE &s, bool binary, int indent); // these other functions are for writing data piece by piece. diff --git a/code/AssetLib/FBX/FBXExporter.cpp b/code/AssetLib/FBX/FBXExporter.cpp index e519f7e77..84a77e18d 100644 --- a/code/AssetLib/FBX/FBXExporter.cpp +++ b/code/AssetLib/FBX/FBXExporter.cpp @@ -498,7 +498,7 @@ void FBXExporter::WriteDocuments () if (!binary) { WriteAsciiSectionHeader("Documents Description"); } - + // not sure what the use of multiple documents would be, // or whether any end-application supports it FBX::Node docs("Documents"); @@ -541,10 +541,17 @@ void FBXExporter::WriteReferences () // (before any actual data is written) // --------------------------------------------------------------- -size_t count_nodes(const aiNode* n) { - size_t count = 1; +size_t count_nodes(const aiNode* n, const aiNode* root) { + size_t count; + if (n == root) { + count = n->mNumMeshes; // (not counting root node) + } else if (n->mNumMeshes > 1) { + count = n->mNumMeshes + 1; + } else { + count = 1; + } for (size_t i = 0; i < n->mNumChildren; ++i) { - count += count_nodes(n->mChildren[i]); + count += count_nodes(n->mChildren[i], root); } return count; } @@ -714,7 +721,7 @@ void FBXExporter::WriteDefinitions () // Model / FbxNode // <~~ node hierarchy - count = int32_t(count_nodes(mScene->mRootNode)) - 1; // (not counting root node) + count = int32_t(count_nodes(mScene->mRootNode, mScene->mRootNode)); if (count) { n = FBX::Node("ObjectType", "Model"); n.AddChild("Count", count); @@ -1251,7 +1258,7 @@ void FBXExporter::WriteObjects () indent = 2; vertexcolors.End(outstream, binary, indent, true); } - + // uvs, if any for (size_t uvi = 0; uvi < m->GetNumUVChannels(); ++uvi) { if (m->mNumUVComponents[uvi] > 2) { @@ -1681,6 +1688,10 @@ void FBXExporter::WriteObjects () // link the image data to the texture connections.emplace_back("C", "OO", image_uid, texture_uid); + aiUVTransform trafo; + unsigned int max = sizeof(aiUVTransform); + aiGetMaterialFloatArray(mat, AI_MATKEY_UVTRANSFORM(aiTextureType_DIFFUSE, 0), (ai_real *)&trafo, &max); + // now write the actual texture node FBX::Node tnode("Texture"); // TODO: some way to determine texture name? @@ -1691,6 +1702,9 @@ void FBXExporter::WriteObjects () tnode.AddChild("Version", int32_t(202)); tnode.AddChild("TextureName", texture_name); FBX::Node p("Properties70"); + p.AddP70vectorA("Translation", trafo.mTranslation[0], trafo.mTranslation[1], 0.0); + p.AddP70vectorA("Rotation", 0, 0, trafo.mRotation); + p.AddP70vectorA("Scaling", trafo.mScaling[0], trafo.mScaling[1], 0.0); p.AddP70enum("CurrentTextureBlendMode", 0); // TODO: verify //p.AddP70string("UVSet", ""); // TODO: how should this work? p.AddP70bool("UseMaterial", 1); @@ -1737,7 +1751,7 @@ void FBXExporter::WriteObjects () bsnode.AddProperty(blendshape_uid); bsnode.AddProperty(blendshape_name + FBX::SEPARATOR + "Blendshape"); bsnode.AddProperty("Shape"); - bsnode.AddChild("Version", int32_t(100)); + bsnode.AddChild("Version", int32_t(100)); bsnode.Begin(outstream, binary, indent); bsnode.DumpProperties(outstream, binary, indent); bsnode.EndProperties(outstream, binary, indent); @@ -1863,7 +1877,7 @@ void FBXExporter::WriteObjects () // at the same time we can build a list of all the skeleton nodes, // which will be used later to mark them as type "limbNode". std::unordered_set limbnodes; - + //actual bone nodes in fbx, without parenting-up std::unordered_set setAllBoneNamesInScene; for(unsigned int m = 0; m < mScene->mNumMeshes; ++ m) @@ -1873,7 +1887,7 @@ void FBXExporter::WriteObjects () setAllBoneNamesInScene.insert(pMesh->mBones[b]->mName.data); } aiMatrix4x4 mxTransIdentity; - + // and a map of nodes by bone name, as finding them is annoying. std::map node_by_bone; for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) { @@ -1942,7 +1956,7 @@ void FBXExporter::WriteObjects () } if (end) { break; } } - + // if it was the skeleton root we can finish here if (end) { break; } } @@ -2196,7 +2210,65 @@ void FBXExporter::WriteObjects () bpnode.Dump(outstream, binary, indent); }*/ - // TODO: cameras, lights + // lights + indent = 1; + lights_uids.clear(); + for (size_t li = 0; li < mScene->mNumLights; ++li) { + aiLight* l = mScene->mLights[li]; + + int64_t uid = generate_uid(); + const std::string lightNodeAttributeName = l->mName.C_Str() + FBX::SEPARATOR + "NodeAttribute"; + + FBX::Node lna("NodeAttribute"); + lna.AddProperties(uid, lightNodeAttributeName, "Light"); + FBX::Node lnap("Properties70"); + + // Light color. + lnap.AddP70colorA("Color", l->mColorDiffuse.r, l->mColorDiffuse.g, l->mColorDiffuse.b); + + // TODO Assimp light description is quite concise and do not handle light intensity. + // Default value to 1000W. + lnap.AddP70numberA("Intensity", 1000); + + // FBXLight::EType conversion + switch (l->mType) { + case aiLightSource_POINT: + lnap.AddP70enum("LightType", 0); + break; + case aiLightSource_DIRECTIONAL: + lnap.AddP70enum("LightType", 1); + break; + case aiLightSource_SPOT: + lnap.AddP70enum("LightType", 2); + lnap.AddP70numberA("InnerAngle", AI_RAD_TO_DEG(l->mAngleInnerCone)); + lnap.AddP70numberA("OuterAngle", AI_RAD_TO_DEG(l->mAngleOuterCone)); + break; + // TODO Assimp do not handle 'area' nor 'volume' lights, but FBX does. + /*case aiLightSource_AREA: + lnap.AddP70enum("LightType", 3); + lnap.AddP70enum("AreaLightShape", 0); // 0=Rectangle, 1=Sphere + break; + case aiLightSource_VOLUME: + lnap.AddP70enum("LightType", 4); + break;*/ + default: + break; + } + + // Did not understood how to configure the decay so disabling attenuation. + lnap.AddP70enum("DecayType", 0); + + // Dump to FBX stream + lna.AddChild(lnap); + lna.AddChild("TypeFlags", FBX::FBXExportProperty("Light")); + lna.AddChild("GeometryVersion", FBX::FBXExportProperty(int32_t(124))); + lna.Dump(outstream, binary, indent); + + // Store name and uid (will be used later when parsing scene nodes) + lights_uids[l->mName.C_Str()] = uid; + } + + // TODO: cameras // write nodes (i.e. model hierarchy) // start at root node @@ -2600,10 +2672,19 @@ void FBXExporter::WriteModelNodes( // and connect them connections.emplace_back("C", "OO", node_attribute_uid, node_uid); } else { - // generate a null node so we can add children to it - WriteModelNode( - outstream, binary, node, node_uid, "Null", transform_chain - ); + const auto& lightIt = lights_uids.find(node->mName.C_Str()); + if(lightIt != lights_uids.end()) { + // Node has a light connected to it. + WriteModelNode( + outstream, binary, node, node_uid, "Light", transform_chain + ); + connections.emplace_back("C", "OO", lightIt->second, node_uid); + } else { + // generate a null node so we can add children to it + WriteModelNode( + outstream, binary, node, node_uid, "Null", transform_chain + ); + } } // if more than one child mesh, make nodes for each mesh @@ -2625,17 +2706,14 @@ void FBXExporter::WriteModelNodes( ], new_node_uid ); - // write model node - FBX::Node m("Model"); + + aiNode new_node; // take name from mesh name, if it exists - std::string name = mScene->mMeshes[node->mMeshes[i]]->mName.C_Str(); - name += FBX::SEPARATOR + "Model"; - m.AddProperties(new_node_uid, name, "Mesh"); - m.AddChild("Version", int32_t(232)); - FBX::Node p("Properties70"); - p.AddP70enum("InheritType", 1); - m.AddChild(p); - m.Dump(outstream, binary, 1); + new_node.mName = mScene->mMeshes[node->mMeshes[i]]->mName; + // write model node + WriteModelNode( + outstream, binary, &new_node, new_node_uid, "Mesh", std::vector>() + ); } } @@ -2647,16 +2725,14 @@ void FBXExporter::WriteModelNodes( } } - void FBXExporter::WriteAnimationCurveNode( - StreamWriterLE& outstream, - int64_t uid, - const std::string& name, // "T", "R", or "S" - aiVector3D default_value, - std::string property_name, // "Lcl Translation" etc - int64_t layer_uid, - int64_t node_uid -) { + StreamWriterLE &outstream, + int64_t uid, + const std::string &name, // "T", "R", or "S" + aiVector3D default_value, + const std::string &property_name, // "Lcl Translation" etc + int64_t layer_uid, + int64_t node_uid) { FBX::Node n("AnimationCurveNode"); n.AddProperties(uid, name + FBX::SEPARATOR + "AnimCurveNode", ""); FBX::Node p("Properties70"); @@ -2671,7 +2747,6 @@ void FBXExporter::WriteAnimationCurveNode( this->connections.emplace_back("C", "OP", uid, node_uid, property_name); } - void FBXExporter::WriteAnimationCurve( StreamWriterLE& outstream, double default_value, diff --git a/code/AssetLib/FBX/FBXExporter.h b/code/AssetLib/FBX/FBXExporter.h index dcd1d2727..d249b7bee 100644 --- a/code/AssetLib/FBX/FBXExporter.h +++ b/code/AssetLib/FBX/FBXExporter.h @@ -63,10 +63,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. struct aiScene; struct aiNode; -//struct aiMaterial; +struct aiLight; -namespace Assimp -{ +namespace Assimp { class IOSystem; class IOStream; class ExportProperties; @@ -95,6 +94,7 @@ namespace Assimp std::vector mesh_uids; std::vector material_uids; std::map node_uids; + std::map lights_uids; // this crude unique-ID system is actually fine int64_t last_uid = 999999; @@ -154,14 +154,13 @@ namespace Assimp FBX::TransformInheritance ti_type=FBX::TransformInheritance_RSrs ); void WriteAnimationCurveNode( - StreamWriterLE& outstream, - int64_t uid, - const std::string& name, // "T", "R", or "S" - aiVector3D default_value, - std::string property_name, // "Lcl Translation" etc - int64_t animation_layer_uid, - int64_t node_uid - ); + StreamWriterLE &outstream, + int64_t uid, + const std::string &name, // "T", "R", or "S" + aiVector3D default_value, + const std::string &property_name, // "Lcl Translation" etc + int64_t animation_layer_uid, + int64_t node_uid); void WriteAnimationCurve( StreamWriterLE& outstream, double default_value, diff --git a/code/AssetLib/FBX/FBXMaterial.cpp b/code/AssetLib/FBX/FBXMaterial.cpp index 6ada9630b..7eb047177 100644 --- a/code/AssetLib/FBX/FBXMaterial.cpp +++ b/code/AssetLib/FBX/FBXMaterial.cpp @@ -142,8 +142,8 @@ Material::~Material() { // ------------------------------------------------------------------------------------------------ Texture::Texture(uint64_t id, const Element& element, const Document& doc, const std::string& name) : - Object(id,element,name), - uvScaling(1.0f,1.0f), + Object(id,element,name), + uvScaling(1.0f,1.0f), media(0) { const Scope& sc = GetRequiredScope(element); @@ -210,6 +210,11 @@ Texture::Texture(uint64_t id, const Element& element, const Document& doc, const uvTrans.y = trans.y; } + const aiVector3D &rotation = PropertyGet(*props, "Rotation", ok); + if (ok) { + uvRotation = rotation.z; + } + // resolve video links if(doc.Settings().readTextures) { const std::vector& conns = doc.GetConnectionsByDestinationSequenced(ID()); @@ -273,8 +278,8 @@ void LayeredTexture::fillTexture(const Document& doc) { // ------------------------------------------------------------------------------------------------ Video::Video(uint64_t id, const Element& element, const Document& doc, const std::string& name) : - Object(id,element,name), - contentLength(0), + Object(id,element,name), + contentLength(0), content(0) { const Scope& sc = GetRequiredScope(element); diff --git a/code/AssetLib/FBX/FBXMeshGeometry.cpp b/code/AssetLib/FBX/FBXMeshGeometry.cpp index 5aecb61b5..6aeebcbe3 100644 --- a/code/AssetLib/FBX/FBXMeshGeometry.cpp +++ b/code/AssetLib/FBX/FBXMeshGeometry.cpp @@ -633,7 +633,7 @@ void MeshGeometry::ReadVertexDataMaterials(std::vector& materials_out, cons { return; } - + // materials are handled separately. First of all, they are assigned per-face // and not per polyvert. Secondly, ReferenceInformationType=IndexToDirect // has a slightly different meaning for materials. diff --git a/code/AssetLib/FBX/FBXMeshGeometry.h b/code/AssetLib/FBX/FBXMeshGeometry.h index ae17860e3..862693b4b 100644 --- a/code/AssetLib/FBX/FBXMeshGeometry.h +++ b/code/AssetLib/FBX/FBXMeshGeometry.h @@ -52,8 +52,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { namespace FBX { -/** - * DOM base class for all kinds of FBX geometry +/** + * DOM base class for all kinds of FBX geometry */ class Geometry : public Object { @@ -76,7 +76,7 @@ private: typedef std::vector MatIndexArray; -/** +/** * DOM class for FBX geometry of type "Mesh" */ class MeshGeometry : public Geometry @@ -84,7 +84,7 @@ class MeshGeometry : public Geometry public: /** The class constructor */ MeshGeometry( uint64_t id, const Element& element, const std::string& name, const Document& doc ); - + /** The class destructor */ virtual ~MeshGeometry(); diff --git a/code/AssetLib/FBX/FBXParser.cpp b/code/AssetLib/FBX/FBXParser.cpp index 37cf83cf9..582940363 100644 --- a/code/AssetLib/FBX/FBXParser.cpp +++ b/code/AssetLib/FBX/FBXParser.cpp @@ -192,6 +192,10 @@ Scope::Scope(Parser& parser,bool topLevel) } const std::string& str = n->StringContents(); + if (str.empty()) { + ParseError("unexpected content: empty string."); + } + elements.insert(ElementMap::value_type(str,new_Element(*n,parser))); // Element() should stop at the next Key token (or right after a Close token) @@ -642,8 +646,7 @@ void ParseVectorDataArray(std::vector& out, const Element& el) ai_assert(data == end); uint64_t dataToRead = static_cast(count) * (type == 'd' ? 8 : 4); - ai_assert(buff.size() == dataToRead); - if (dataToRead > buff.size()) { + if (dataToRead != buff.size()) { ParseError("Invalid read size (binary)",&el); } @@ -733,8 +736,7 @@ void ParseVectorDataArray(std::vector& out, const Element& el) ai_assert(data == end); uint64_t dataToRead = static_cast(count) * (type == 'd' ? 8 : 4); - ai_assert(buff.size() == dataToRead); - if (dataToRead > buff.size()) { + if (dataToRead != buff.size()) { ParseError("Invalid read size (binary)",&el); } @@ -816,8 +818,7 @@ void ParseVectorDataArray(std::vector& out, const Element& el) ai_assert(data == end); uint64_t dataToRead = static_cast(count) * (type == 'd' ? 8 : 4); - ai_assert(buff.size() == dataToRead); - if (dataToRead > buff.size()) { + if (dataToRead != buff.size()) { ParseError("Invalid read size (binary)",&el); } @@ -892,8 +893,7 @@ void ParseVectorDataArray(std::vector& out, const Element& el) ai_assert(data == end); uint64_t dataToRead = static_cast(count) * 4; - ai_assert(buff.size() == dataToRead); - if (dataToRead > buff.size()) { + if (dataToRead != buff.size()) { ParseError("Invalid read size (binary)",&el); } @@ -954,8 +954,7 @@ void ParseVectorDataArray(std::vector& out, const Element& el) ai_assert(data == end); uint64_t dataToRead = static_cast(count) * (type == 'd' ? 8 : 4); - ai_assert(buff.size() == dataToRead); - if (dataToRead > buff.size()) { + if (dataToRead != buff.size()) { ParseError("Invalid read size (binary)",&el); } @@ -1019,8 +1018,7 @@ void ParseVectorDataArray(std::vector& out, const Element& el) ai_assert(data == end); uint64_t dataToRead = static_cast(count) * 4; - ai_assert(buff.size() == dataToRead); - if (dataToRead > buff.size()) { + if (dataToRead != buff.size()) { ParseError("Invalid read size (binary)",&el); } @@ -1088,8 +1086,7 @@ void ParseVectorDataArray(std::vector& out, const Element& el) ai_assert(data == end); uint64_t dataToRead = static_cast(count) * 8; - ai_assert(buff.size() == dataToRead); - if (dataToRead > buff.size()) { + if (dataToRead != buff.size()) { ParseError("Invalid read size (binary)",&el); } @@ -1150,8 +1147,7 @@ void ParseVectorDataArray(std::vector& out, const Element& el) ai_assert(data == end); uint64_t dataToRead = static_cast(count) * 8; - ai_assert(buff.size() == dataToRead); - if (dataToRead > buff.size()) { + if (dataToRead != buff.size()) { ParseError("Invalid read size (binary)",&el); } diff --git a/code/AssetLib/FBX/FBXProperties.cpp b/code/AssetLib/FBX/FBXProperties.cpp index 1a5ebffd1..c3f4de260 100644 --- a/code/AssetLib/FBX/FBXProperties.cpp +++ b/code/AssetLib/FBX/FBXProperties.cpp @@ -52,6 +52,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "FBXDocumentUtil.h" #include "FBXProperties.h" +#include + namespace Assimp { namespace FBX { @@ -172,10 +174,8 @@ PropertyTable::PropertyTable() } // ------------------------------------------------------------------------------------------------ -PropertyTable::PropertyTable(const Element& element, std::shared_ptr templateProps) -: templateProps(templateProps) -, element(&element) -{ +PropertyTable::PropertyTable(const Element &element, std::shared_ptr templateProps) : + templateProps(std::move(templateProps)), element(&element) { const Scope& scope = GetRequiredScope(element); for(const ElementMap::value_type& v : scope.Elements()) { if(v.first != "P") { @@ -199,7 +199,6 @@ PropertyTable::PropertyTable(const Element& element, std::shared_ptr > DirectPro typedef std::fbx_unordered_map PropertyMap; typedef std::fbx_unordered_map LazyPropertyMap; -/** +/** * Represents a property table as can be found in the newer FBX files (Properties60, Properties70) */ class PropertyTable { @@ -130,7 +130,7 @@ private: // ------------------------------------------------------------------------------------------------ template -inline +inline T PropertyGet(const PropertyTable& in, const std::string& name, const T& defaultValue) { const Property* const prop = in.Get(name); if( nullptr == prop) { @@ -148,7 +148,7 @@ T PropertyGet(const PropertyTable& in, const std::string& name, const T& default // ------------------------------------------------------------------------------------------------ template -inline +inline T PropertyGet(const PropertyTable& in, const std::string& name, bool& result, bool useTemplate=false ) { const Property* prop = in.Get(name); if( nullptr == prop) { diff --git a/code/AssetLib/FBX/FBXUtil.cpp b/code/AssetLib/FBX/FBXUtil.cpp index 66abf0565..3fe791b97 100644 --- a/code/AssetLib/FBX/FBXUtil.cpp +++ b/code/AssetLib/FBX/FBXUtil.cpp @@ -101,7 +101,7 @@ std::string GetLineAndColumnText(unsigned int line, unsigned int column) std::string GetTokenText(const Token* tok) { if(tok->IsBinary()) { - return static_cast( Formatter::format() << + return static_cast( Formatter::format() << " (" << TokenTypeString(tok->Type()) << ", offset 0x" << std::hex << tok->Offset() << ") " ); } diff --git a/code/AssetLib/HMP/HMPLoader.cpp b/code/AssetLib/HMP/HMPLoader.cpp index cd14cb9c3..97c1858fb 100644 --- a/code/AssetLib/HMP/HMPLoader.cpp +++ b/code/AssetLib/HMP/HMPLoader.cpp @@ -153,10 +153,10 @@ void HMPImporter::InternReadFile(const std::string &pFile, } else { // Print the magic word to the logger std::string szBuffer = ai_str_toprintable((const char *)&iMagic, sizeof(iMagic)); - + delete[] mBuffer; mBuffer = nullptr; - + // We're definitely unable to load this file throw DeadlyImportError("Unknown HMP subformat ", pFile, ". Magic word (", szBuffer, ") is not known"); diff --git a/code/AssetLib/IFC/IFCBoolean.cpp b/code/AssetLib/IFC/IFCBoolean.cpp index 86cac7f46..afd0ad6eb 100644 --- a/code/AssetLib/IFC/IFCBoolean.cpp +++ b/code/AssetLib/IFC/IFCBoolean.cpp @@ -513,7 +513,7 @@ void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const Schema_2x3::IfcPoly } // we got a list of in-out-combinations of intersections. That should be an even number of intersections, or - // we're fucked. + // we are facing a non-recoverable error. if ((intersections.size() & 1) != 0) { IFCImporter::LogWarn("Odd number of intersections, can't work with that. Omitting half space boundary check."); continue; diff --git a/code/AssetLib/IFC/IFCCurve.cpp b/code/AssetLib/IFC/IFCCurve.cpp index 28cd9690c..3ded43bc0 100644 --- a/code/AssetLib/IFC/IFCCurve.cpp +++ b/code/AssetLib/IFC/IFCCurve.cpp @@ -514,7 +514,7 @@ IfcFloat Curve::GetParametricRangeDelta() const { // ------------------------------------------------------------------------------------------------ size_t Curve::EstimateSampleCount(IfcFloat a, IfcFloat b) const { - (void)(a); (void)(b); + (void)(a); (void)(b); ai_assert( InRange( a ) ); ai_assert( InRange( b ) ); diff --git a/code/AssetLib/IFC/IFCGeometry.cpp b/code/AssetLib/IFC/IFCGeometry.cpp index 6e645f1ae..e70c760d8 100644 --- a/code/AssetLib/IFC/IFCGeometry.cpp +++ b/code/AssetLib/IFC/IFCGeometry.cpp @@ -740,7 +740,7 @@ bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, unsigned bool fix_orientation = false; std::shared_ptr< TempMesh > meshtmp = std::make_shared(); if(const Schema_2x3::IfcShellBasedSurfaceModel* shellmod = geo.ToPtr()) { - for(std::shared_ptr shell :shellmod->SbsmBoundary) { + for (const std::shared_ptr &shell : shellmod->SbsmBoundary) { try { const ::Assimp::STEP::EXPRESS::ENTITY& e = shell->To<::Assimp::STEP::EXPRESS::ENTITY>(); const Schema_2x3::IfcConnectedFaceSet& fs = conv.db.MustGetObject(e).To(); diff --git a/code/AssetLib/IFC/IFCMaterial.cpp b/code/AssetLib/IFC/IFCMaterial.cpp index 2a79f0754..c26a3aa0a 100644 --- a/code/AssetLib/IFC/IFCMaterial.cpp +++ b/code/AssetLib/IFC/IFCMaterial.cpp @@ -75,7 +75,7 @@ static void FillMaterial(aiMaterial* mat,const IFC::Schema_2x3::IfcSurfaceStyle* mat->AddProperty(&name,AI_MATKEY_NAME); // now see which kinds of surface information are present - for(std::shared_ptr< const IFC::Schema_2x3::IfcSurfaceStyleElementSelect > sel2 : surf->Styles) { + for (const std::shared_ptr &sel2 : surf->Styles) { if (const IFC::Schema_2x3::IfcSurfaceStyleShading* shade = sel2->ResolveSelectPtr(conv.db)) { aiColor4D col_base,col; @@ -124,7 +124,7 @@ static void FillMaterial(aiMaterial* mat,const IFC::Schema_2x3::IfcSurfaceStyle* } } } - } + } } } @@ -134,7 +134,7 @@ unsigned int ProcessMaterials(uint64_t id, unsigned int prevMatId, ConversionDat for(;range.first != range.second; ++range.first) { if(const IFC::Schema_2x3::IfcStyledItem* const styled = conv.db.GetObject((*range.first).second)->ToPtr()) { for(const IFC::Schema_2x3::IfcPresentationStyleAssignment& as : styled->Styles) { - for(std::shared_ptr sel : as.Styles) { + for (const std::shared_ptr &sel : as.Styles) { if( const IFC::Schema_2x3::IfcSurfaceStyle* const surf = sel->ResolveSelectPtr(conv.db) ) { // try to satisfy from cache diff --git a/code/AssetLib/IFC/IFCOpenings.cpp b/code/AssetLib/IFC/IFCOpenings.cpp index b7665582c..d6671b885 100644 --- a/code/AssetLib/IFC/IFCOpenings.cpp +++ b/code/AssetLib/IFC/IFCOpenings.cpp @@ -911,14 +911,14 @@ size_t CloseWindows(ContourVector& contours, // compare base poly normal and contour normal to detect if we need to reverse the face winding if(curmesh.mVertcnt.size() > 0) { IfcVector3 basePolyNormal = TempMesh::ComputePolygonNormal(curmesh.mVerts.data(), curmesh.mVertcnt.front()); - + std::vector worldSpaceContourVtx(it->contour.size()); - + for(size_t a = 0; a < it->contour.size(); ++a) worldSpaceContourVtx[a] = minv * IfcVector3(it->contour[a].x, it->contour[a].y, 0.0); - + IfcVector3 contourNormal = TempMesh::ComputePolygonNormal(worldSpaceContourVtx.data(), worldSpaceContourVtx.size()); - + reverseCountourFaces = (contourNormal * basePolyNormal) > 0.0; } diff --git a/code/AssetLib/IFC/IFCReaderGen1_2x3.cpp b/code/AssetLib/IFC/IFCReaderGen1_2x3.cpp index 2cfa22530..a6f7ae3eb 100644 --- a/code/AssetLib/IFC/IFCReaderGen1_2x3.cpp +++ b/code/AssetLib/IFC/IFCReaderGen1_2x3.cpp @@ -5,8 +5,8 @@ Open Asset Import Library (ASSIMP) Copyright (c) 2006-2020, ASSIMP Development Team All rights reserved. -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the +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 @@ -23,16 +23,16 @@ following conditions are met: derived from this software without specific prior written permission of the ASSIMP Development Team. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +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 +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 +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 +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. ---------------------------------------------------------------------- @@ -1063,27 +1063,27 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcRoo if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcRoot"); } do { // convert the 'GlobalId' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->GlobalId, arg, db ); break; } + try { GenericConvert( in->GlobalId, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRoot to be a `IfcGloballyUniqueId`")); } } while(0); do { // convert the 'OwnerHistory' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->OwnerHistory, arg, db ); break; } + try { GenericConvert( in->OwnerHistory, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRoot to be a `IfcOwnerHistory`")); } } while(0); do { // convert the 'Name' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Name, arg, db ); break; } + try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcRoot to be a `IfcLabel`")); } } while(0); do { // convert the 'Description' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Description, arg, db ); break; } + try { GenericConvert( in->Description, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRoot to be a `IfcText`")); } } while(0); return base; @@ -1150,27 +1150,27 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcRepresentation"); } do { // convert the 'ContextOfItems' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->ContextOfItems, arg, db ); break; } + try { GenericConvert( in->ContextOfItems, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRepresentation to be a `IfcRepresentationContext`")); } } while(0); do { // convert the 'RepresentationIdentifier' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->RepresentationIdentifier, arg, db ); break; } + try { GenericConvert( in->RepresentationIdentifier, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRepresentation to be a `IfcLabel`")); } } while(0); do { // convert the 'RepresentationType' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->RepresentationType, arg, db ); break; } + try { GenericConvert( in->RepresentationType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcRepresentation to be a `IfcLabel`")); } } while(0); do { // convert the 'Items' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } - try { GenericConvert( in->Items, arg, db ); break; } + try { GenericConvert( in->Items, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRepresentation to be a `SET [1:?] OF IfcRepresentationItem`")); } } while(0); return base; @@ -1237,7 +1237,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcO std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ObjectType, arg, db ); break; } + try { GenericConvert( in->ObjectType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcObject to be a `IfcLabel`")); } } while(0); return base; @@ -1290,20 +1290,20 @@ template <> size_t GenericFill(const DB& db, const LIS std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Name, arg, db ); break; } + try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcProductRepresentation to be a `IfcLabel`")); } } while(0); do { // convert the 'Description' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Description, arg, db ); break; } + try { GenericConvert( in->Description, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcProductRepresentation to be a `IfcText`")); } } while(0); do { // convert the 'Representations' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } - try { GenericConvert( in->Representations, arg, db ); break; } + try { GenericConvert( in->Representations, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcProductRepresentation to be a `LIST [1:?] OF IfcRepresentation`")); } } while(0); return base; @@ -1316,14 +1316,14 @@ template <> size_t GenericFill(const DB& db, const LIST& params, Ifc std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ObjectPlacement, arg, db ); break; } + try { GenericConvert( in->ObjectPlacement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcProduct to be a `IfcObjectPlacement`")); } } while(0); do { // convert the 'Representation' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Representation, arg, db ); break; } + try { GenericConvert( in->Representation, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcProduct to be a `IfcProductRepresentation`")); } } while(0); return base; @@ -1336,7 +1336,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, Ifc std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Tag, arg, db ); break; } + try { GenericConvert( in->Tag, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcElement to be a `IfcIdentifier`")); } } while(0); return base; @@ -1374,13 +1374,13 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcCompositeCurve"); } do { // convert the 'Segments' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Segments, arg, db ); break; } + try { GenericConvert( in->Segments, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCompositeCurve to be a `LIST [1:?] OF IfcCompositeCurveSegment`")); } } while(0); do { // convert the 'SelfIntersect' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->SelfIntersect, arg, db ); break; } + try { GenericConvert( in->SelfIntersect, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCompositeCurve to be a `LOGICAL`")); } } while(0); return base; @@ -1400,27 +1400,27 @@ template <> size_t GenericFill(const DB& db, std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Axis1, arg, db ); break; } + try { GenericConvert( in->Axis1, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCartesianTransformationOperator to be a `IfcDirection`")); } } while(0); do { // convert the 'Axis2' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Axis2, arg, db ); break; } + try { GenericConvert( in->Axis2, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCartesianTransformationOperator to be a `IfcDirection`")); } } while(0); do { // convert the 'LocalOrigin' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } - try { GenericConvert( in->LocalOrigin, arg, db ); break; } + try { GenericConvert( in->LocalOrigin, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcCartesianTransformationOperator to be a `IfcCartesianPoint`")); } } while(0); do { // convert the 'Scale' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Scale, arg, db ); break; } + try { GenericConvert( in->Scale, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcCartesianTransformationOperator to be a `REAL`")); } } while(0); return base; @@ -1433,7 +1433,7 @@ template <> size_t GenericFill(const DB& d std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Axis3, arg, db ); break; } + try { GenericConvert( in->Axis3, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcCartesianTransformationOperator3D to be a `IfcDirection`")); } } while(0); return base; @@ -1445,14 +1445,14 @@ template <> size_t GenericFill(const DB& db, const LIST& params, If if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcProperty"); } do { // convert the 'Name' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Name, arg, db ); break; } + try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcProperty to be a `IfcIdentifier`")); } } while(0); do { // convert the 'Description' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Description, arg, db ); break; } + try { GenericConvert( in->Description, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcProperty to be a `IfcText`")); } } while(0); return base; @@ -1497,7 +1497,7 @@ template <> size_t GenericFill(const DB& db, const LIST& p if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcElementarySurface"); } do { // convert the 'Position' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Position, arg, db ); break; } + try { GenericConvert( in->Position, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcElementarySurface to be a `IfcAxis2Placement3D`")); } } while(0); return base; @@ -1515,19 +1515,19 @@ template <> size_t GenericFill(const DB& db, const LIST& param if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcBooleanResult"); } do { // convert the 'Operator' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Operator, arg, db ); break; } + try { GenericConvert( in->Operator, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcBooleanResult to be a `IfcBooleanOperator`")); } } while(0); do { // convert the 'FirstOperand' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->FirstOperand, arg, db ); break; } + try { GenericConvert( in->FirstOperand, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcBooleanResult to be a `IfcBooleanOperand`")); } } while(0); do { // convert the 'SecondOperand' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } - try { GenericConvert( in->SecondOperand, arg, db ); break; } + try { GenericConvert( in->SecondOperand, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcBooleanResult to be a `IfcBooleanOperand`")); } } while(0); return base; @@ -1551,7 +1551,7 @@ template <> size_t GenericFill(const DB& db, const LIST& p if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcManifoldSolidBrep"); } do { // convert the 'Outer' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Outer, arg, db ); break; } + try { GenericConvert( in->Outer, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcManifoldSolidBrep to be a `IfcClosedShell`")); } } while(0); return base; @@ -1630,12 +1630,12 @@ template <> size_t GenericFill(const DB& db, const LIST& par size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelFillsElement"); } do { // convert the 'RelatingOpeningElement' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->RelatingOpeningElement, arg, db ); break; } + try { GenericConvert( in->RelatingOpeningElement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelFillsElement to be a `IfcOpeningElement`")); } } while(0); do { // convert the 'RelatedBuildingElement' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->RelatedBuildingElement, arg, db ); break; } + try { GenericConvert( in->RelatedBuildingElement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelFillsElement to be a `IfcElement`")); } } while(0); return base; @@ -1681,12 +1681,12 @@ template <> size_t GenericFill(const DB& db, size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelContainedInSpatialStructure"); } do { // convert the 'RelatedElements' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->RelatedElements, arg, db ); break; } + try { GenericConvert( in->RelatedElements, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelContainedInSpatialStructure to be a `SET [1:?] OF IfcProduct`")); } } while(0); do { // convert the 'RelatingStructure' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->RelatingStructure, arg, db ); break; } + try { GenericConvert( in->RelatingStructure, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelContainedInSpatialStructure to be a `IfcSpatialStructureElement`")); } } while(0); return base; @@ -1772,7 +1772,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, I size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcDirection"); } do { // convert the 'DirectionRatios' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->DirectionRatios, arg, db ); break; } + try { GenericConvert( in->DirectionRatios, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcDirection to be a `LIST [2:3] OF REAL`")); } } while(0); return base; @@ -1784,14 +1784,14 @@ template <> size_t GenericFill(const DB& db, const LIST& params, if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcProfileDef"); } do { // convert the 'ProfileType' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->ProfileType, arg, db ); break; } + try { GenericConvert( in->ProfileType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcProfileDef to be a `IfcProfileTypeEnum`")); } } while(0); do { // convert the 'ProfileName' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ProfileName, arg, db ); break; } + try { GenericConvert( in->ProfileName, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcProfileDef to be a `IfcLabel`")); } } while(0); return base; @@ -1803,7 +1803,7 @@ template <> size_t GenericFill(const DB& db, const L if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcParameterizedProfileDef"); } do { // convert the 'Position' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Position, arg, db ); break; } + try { GenericConvert( in->Position, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcParameterizedProfileDef to be a `IfcAxis2Placement2D`")); } } while(0); return base; @@ -1910,7 +1910,7 @@ template <> size_t GenericFill(const DB& db, const LIST& pa if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcCircleProfileDef"); } do { // convert the 'Radius' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Radius, arg, db ); break; } + try { GenericConvert( in->Radius, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcCircleProfileDef to be a `IfcPositiveLengthMeasure`")); } } while(0); return base; @@ -1921,7 +1921,7 @@ template <> size_t GenericFill(const DB& db, const LI size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcCircleHollowProfileDef"); } do { // convert the 'WallThickness' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->WallThickness, arg, db ); break; } + try { GenericConvert( in->WallThickness, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcCircleHollowProfileDef to be a `IfcPositiveLengthMeasure`")); } } while(0); return base; @@ -1933,7 +1933,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, I if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPlacement"); } do { // convert the 'Location' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Location, arg, db ); break; } + try { GenericConvert( in->Location, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPlacement to be a `IfcCartesianPoint`")); } } while(0); return base; @@ -1945,13 +1945,13 @@ template <> size_t GenericFill(const DB& db, const LIST& pa if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcAxis2Placement3D"); } do { // convert the 'Axis' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Axis, arg, db ); break; } + try { GenericConvert( in->Axis, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcAxis2Placement3D to be a `IfcDirection`")); } } while(0); do { // convert the 'RefDirection' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->RefDirection, arg, db ); break; } + try { GenericConvert( in->RefDirection, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcAxis2Placement3D to be a `IfcDirection`")); } } while(0); return base; @@ -1964,7 +1964,7 @@ template <> size_t GenericFill(const DB& db, const LIST& p std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Name, arg, db ); break; } + try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPresentationStyle to be a `IfcLabel`")); } } while(0); return base; @@ -1982,17 +1982,17 @@ template <> size_t GenericFill(const DB& db, const LIS size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcCompositeCurveSegment"); } do { // convert the 'Transition' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Transition, arg, db ); break; } + try { GenericConvert( in->Transition, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCompositeCurveSegment to be a `IfcTransitionCode`")); } } while(0); do { // convert the 'SameSense' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->SameSense, arg, db ); break; } + try { GenericConvert( in->SameSense, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCompositeCurveSegment to be a `BOOLEAN`")); } } while(0); do { // convert the 'ParentCurve' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->ParentCurve, arg, db ); break; } + try { GenericConvert( in->ParentCurve, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcCompositeCurveSegment to be a `IfcCurve`")); } } while(0); return base; @@ -2004,13 +2004,13 @@ template <> size_t GenericFill(const DB& db, const LIST& if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcRectangleProfileDef"); } do { // convert the 'XDim' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->XDim, arg, db ); break; } + try { GenericConvert( in->XDim, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRectangleProfileDef to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'YDim' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->YDim, arg, db ); break; } + try { GenericConvert( in->YDim, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRectangleProfileDef to be a `IfcPositiveLengthMeasure`")); } } while(0); return base; @@ -2106,12 +2106,12 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcLocalPlacement"); } do { // convert the 'PlacementRelTo' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->PlacementRelTo, arg, db ); break; } + try { GenericConvert( in->PlacementRelTo, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcLocalPlacement to be a `IfcObjectPlacement`")); } } while(0); do { // convert the 'RelativePlacement' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->RelativePlacement, arg, db ); break; } + try { GenericConvert( in->RelativePlacement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcLocalPlacement to be a `IfcAxis2Placement`")); } } while(0); return base; @@ -2123,13 +2123,13 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcSweptAreaSolid"); } do { // convert the 'SweptArea' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->SweptArea, arg, db ); break; } + try { GenericConvert( in->SweptArea, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSweptAreaSolid to be a `IfcProfileDef`")); } } while(0); do { // convert the 'Position' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->Position, arg, db ); break; } + try { GenericConvert( in->Position, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSweptAreaSolid to be a `IfcAxis2Placement3D`")); } } while(0); return base; @@ -2140,12 +2140,12 @@ template <> size_t GenericFill(const DB& db, const LIST& p size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcRevolvedAreaSolid"); } do { // convert the 'Axis' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Axis, arg, db ); break; } + try { GenericConvert( in->Axis, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcRevolvedAreaSolid to be a `IfcAxis1Placement`")); } } while(0); do { // convert the 'Angle' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Angle, arg, db ); break; } + try { GenericConvert( in->Angle, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRevolvedAreaSolid to be a `IfcPlaneAngleMeasure`")); } } while(0); return base; @@ -2170,28 +2170,28 @@ template <> size_t GenericFill(const DB& db, const LIST& para size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcSweptDiskSolid"); } do { // convert the 'Directrix' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Directrix, arg, db ); break; } + try { GenericConvert( in->Directrix, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSweptDiskSolid to be a `IfcCurve`")); } } while(0); do { // convert the 'Radius' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Radius, arg, db ); break; } + try { GenericConvert( in->Radius, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSweptDiskSolid to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'InnerRadius' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->InnerRadius, arg, db ); break; } + try { GenericConvert( in->InnerRadius, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSweptDiskSolid to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'StartParam' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->StartParam, arg, db ); break; } + try { GenericConvert( in->StartParam, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcSweptDiskSolid to be a `IfcParameterValue`")); } } while(0); do { // convert the 'EndParam' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->EndParam, arg, db ); break; } + try { GenericConvert( in->EndParam, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcSweptDiskSolid to be a `IfcParameterValue`")); } } while(0); return base; @@ -2203,13 +2203,13 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcHalfSpaceSolid"); } do { // convert the 'BaseSurface' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->BaseSurface, arg, db ); break; } + try { GenericConvert( in->BaseSurface, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcHalfSpaceSolid to be a `IfcSurface`")); } } while(0); do { // convert the 'AgreementFlag' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->AgreementFlag, arg, db ); break; } + try { GenericConvert( in->AgreementFlag, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcHalfSpaceSolid to be a `BOOLEAN`")); } } while(0); return base; @@ -2220,12 +2220,12 @@ template <> size_t GenericFill(const DB& db, const size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcPolygonalBoundedHalfSpace"); } do { // convert the 'Position' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Position, arg, db ); break; } + try { GenericConvert( in->Position, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcPolygonalBoundedHalfSpace to be a `IfcAxis2Placement3D`")); } } while(0); do { // convert the 'PolygonalBoundary' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->PolygonalBoundary, arg, db ); break; } + try { GenericConvert( in->PolygonalBoundary, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcPolygonalBoundedHalfSpace to be a `IfcBoundedCurve`")); } } while(0); return base; @@ -2251,23 +2251,23 @@ template <> size_t GenericFill(const DB& db, const LIST& params, Ifc if (params.GetSize() < 9) { throw STEP::TypeError("expected 9 arguments to IfcProject"); } do { // convert the 'LongName' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->LongName, arg, db ); break; } + try { GenericConvert( in->LongName, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcProject to be a `IfcLabel`")); } } while(0); do { // convert the 'Phase' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Phase, arg, db ); break; } + try { GenericConvert( in->Phase, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcProject to be a `IfcLabel`")); } } while(0); do { // convert the 'RepresentationContexts' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->RepresentationContexts, arg, db ); break; } + try { GenericConvert( in->RepresentationContexts, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcProject to be a `SET [1:?] OF IfcRepresentationContext`")); } } while(0); do { // convert the 'UnitsInContext' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->UnitsInContext, arg, db ); break; } + try { GenericConvert( in->UnitsInContext, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcProject to be a `IfcUnitAssignment`")); } } while(0); return base; @@ -2327,27 +2327,27 @@ template <> size_t GenericFill(const DB& db, const LIST& params size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcTrimmedCurve"); } do { // convert the 'BasisCurve' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->BasisCurve, arg, db ); break; } + try { GenericConvert( in->BasisCurve, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcTrimmedCurve to be a `IfcCurve`")); } } while(0); do { // convert the 'Trim1' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Trim1, arg, db ); break; } + try { GenericConvert( in->Trim1, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcTrimmedCurve to be a `SET [1:2] OF IfcTrimmingSelect`")); } } while(0); do { // convert the 'Trim2' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Trim2, arg, db ); break; } + try { GenericConvert( in->Trim2, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcTrimmedCurve to be a `SET [1:2] OF IfcTrimmingSelect`")); } } while(0); do { // convert the 'SenseAgreement' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->SenseAgreement, arg, db ); break; } + try { GenericConvert( in->SenseAgreement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcTrimmedCurve to be a `BOOLEAN`")); } } while(0); do { // convert the 'MasterRepresentation' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->MasterRepresentation, arg, db ); break; } + try { GenericConvert( in->MasterRepresentation, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcTrimmedCurve to be a `IfcTrimmingPreference`")); } } while(0); return base; @@ -2359,7 +2359,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcRelDefines"); } do { // convert the 'RelatedObjects' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->RelatedObjects, arg, db ); break; } + try { GenericConvert( in->RelatedObjects, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelDefines to be a `SET [1:?] OF IfcObject`")); } } while(0); return base; @@ -2371,7 +2371,7 @@ template <> size_t GenericFill(const DB& db, const LI if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelDefinesByProperties"); } do { // convert the 'RelatingPropertyDefinition' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->RelatingPropertyDefinition, arg, db ); break; } + try { GenericConvert( in->RelatingPropertyDefinition, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelDefinesByProperties to be a `IfcPropertySetDefinition`")); } } while(0); return base; @@ -2404,7 +2404,7 @@ template <> size_t GenericFill(const DB& db, const L if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcArbitraryOpenProfileDef"); } do { // convert the 'Curve' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Curve, arg, db ); break; } + try { GenericConvert( in->Curve, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcArbitraryOpenProfileDef to be a `IfcBoundedCurve`")); } } while(0); return base; @@ -2570,13 +2570,13 @@ template <> size_t GenericFill(const DB& db, const LIST& param if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelDecomposes"); } do { // convert the 'RelatingObject' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->RelatingObject, arg, db ); break; } + try { GenericConvert( in->RelatingObject, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelDecomposes to be a `IfcObjectDefinition`")); } } while(0); do { // convert the 'RelatedObjects' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->RelatedObjects, arg, db ); break; } + try { GenericConvert( in->RelatedObjects, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelDecomposes to be a `SET [1:?] OF IfcObjectDefinition`")); } } while(0); return base; @@ -2594,7 +2594,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, If size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPolyline"); } do { // convert the 'Points' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Points, arg, db ); break; } + try { GenericConvert( in->Points, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPolyline to be a `LIST [2:?] OF IfcCartesianPoint`")); } } while(0); return base; @@ -2626,12 +2626,12 @@ template <> size_t GenericFill(const DB& db, const LIST& params, size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcMappedItem"); } do { // convert the 'MappingSource' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->MappingSource, arg, db ); break; } + try { GenericConvert( in->MappingSource, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcMappedItem to be a `IfcRepresentationMap`")); } } while(0); do { // convert the 'MappingTarget' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->MappingTarget, arg, db ); break; } + try { GenericConvert( in->MappingTarget, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcMappedItem to be a `IfcCartesianTransformationOperator`")); } } while(0); return base; @@ -2658,13 +2658,13 @@ template <> size_t GenericFill(const DB& db, const LIST& params, I std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Dimensions, arg, db ); break; } + try { GenericConvert( in->Dimensions, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcNamedUnit to be a `IfcDimensionalExponents`")); } } while(0); do { // convert the 'UnitType' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->UnitType, arg, db ); break; } + try { GenericConvert( in->UnitType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcNamedUnit to be a `IfcUnitEnum`")); } } while(0); return base; @@ -2719,13 +2719,13 @@ template <> size_t GenericFill(const DB& db, const L std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->LongName, arg, db ); break; } + try { GenericConvert( in->LongName, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcSpatialStructureElement to be a `IfcLabel`")); } } while(0); do { // convert the 'CompositionType' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->CompositionType, arg, db ); break; } + try { GenericConvert( in->CompositionType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcSpatialStructureElement to be a `IfcElementCompositionEnum`")); } } while(0); return base; @@ -2737,19 +2737,19 @@ template <> size_t GenericFill(const DB& db, const LIST& params, If if (params.GetSize() < 12) { throw STEP::TypeError("expected 12 arguments to IfcBuilding"); } do { // convert the 'ElevationOfRefHeight' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ElevationOfRefHeight, arg, db ); break; } + try { GenericConvert( in->ElevationOfRefHeight, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcBuilding to be a `IfcLengthMeasure`")); } } while(0); do { // convert the 'ElevationOfTerrain' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ElevationOfTerrain, arg, db ); break; } + try { GenericConvert( in->ElevationOfTerrain, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 10 to IfcBuilding to be a `IfcLengthMeasure`")); } } while(0); do { // convert the 'BuildingAddress' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->BuildingAddress, arg, db ); break; } + try { GenericConvert( in->BuildingAddress, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 11 to IfcBuilding to be a `IfcPostalAddress`")); } } while(0); return base; @@ -2761,7 +2761,7 @@ template <> size_t GenericFill(const DB& db, const LIST& pa if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcConnectedFaceSet"); } do { // convert the 'CfsFaces' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->CfsFaces, arg, db ); break; } + try { GenericConvert( in->CfsFaces, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcConnectedFaceSet to be a `SET [1:?] OF IfcFace`")); } } while(0); return base; @@ -2787,7 +2787,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcCo if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcConic"); } do { // convert the 'Position' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Position, arg, db ); break; } + try { GenericConvert( in->Position, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcConic to be a `IfcAxis2Placement`")); } } while(0); return base; @@ -2834,32 +2834,32 @@ template <> size_t GenericFill(const DB& db, const LIST& pa if (params.GetSize() < 8) { throw STEP::TypeError("expected 8 arguments to IfcIShapeProfileDef"); } do { // convert the 'OverallWidth' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->OverallWidth, arg, db ); break; } + try { GenericConvert( in->OverallWidth, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'OverallDepth' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->OverallDepth, arg, db ); break; } + try { GenericConvert( in->OverallDepth, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'WebThickness' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } - try { GenericConvert( in->WebThickness, arg, db ); break; } + try { GenericConvert( in->WebThickness, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'FlangeThickness' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } - try { GenericConvert( in->FlangeThickness, arg, db ); break; } + try { GenericConvert( in->FlangeThickness, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'FilletRadius' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[4]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->FilletRadius, arg, db ); break; } + try { GenericConvert( in->FilletRadius, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } } while(0); return base; @@ -2933,13 +2933,13 @@ template <> size_t GenericFill(const DB& db, const LIST& p size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcPropertyListValue"); } do { // convert the 'ListValues' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->ListValues, arg, db ); break; } + try { GenericConvert( in->ListValues, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcPropertyListValue to be a `LIST [1:?] OF IfcValue`")); } } while(0); do { // convert the 'Unit' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Unit, arg, db ); break; } + try { GenericConvert( in->Unit, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcPropertyListValue to be a `IfcUnit`")); } } while(0); return base; @@ -2965,13 +2965,13 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcDoo if (params.GetSize() < 10) { throw STEP::TypeError("expected 10 arguments to IfcDoor"); } do { // convert the 'OverallHeight' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->OverallHeight, arg, db ); break; } + try { GenericConvert( in->OverallHeight, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcDoor to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'OverallWidth' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->OverallWidth, arg, db ); break; } + try { GenericConvert( in->OverallWidth, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcDoor to be a `IfcPositiveLengthMeasure`")); } } while(0); return base; @@ -2984,20 +2984,20 @@ template <> size_t GenericFill(const DB& db, const LIST& params, std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Item, arg, db ); break; } + try { GenericConvert( in->Item, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcStyledItem to be a `IfcRepresentationItem`")); } } while(0); do { // convert the 'Styles' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->Styles, arg, db ); break; } + try { GenericConvert( in->Styles, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcStyledItem to be a `SET [1:?] OF IfcPresentationStyleAssignment`")); } } while(0); do { // convert the 'Name' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Name, arg, db ); break; } + try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcStyledItem to be a `IfcLabel`")); } } while(0); return base; @@ -3023,7 +3023,7 @@ template <> size_t GenericFill(const DB& db, const if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcArbitraryClosedProfileDef"); } do { // convert the 'OuterCurve' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->OuterCurve, arg, db ); break; } + try { GenericConvert( in->OuterCurve, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcArbitraryClosedProfileDef to be a `IfcCurve`")); } } while(0); return base; @@ -3041,12 +3041,12 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcLin size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcLine"); } do { // convert the 'Pnt' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Pnt, arg, db ); break; } + try { GenericConvert( in->Pnt, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcLine to be a `IfcCartesianPoint`")); } } while(0); do { // convert the 'Dir' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Dir, arg, db ); break; } + try { GenericConvert( in->Dir, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcLine to be a `IfcVector`")); } } while(0); return base; @@ -3072,13 +3072,13 @@ template <> size_t GenericFill(const DB& db, const LIST& if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcPropertySingleValue"); } do { // convert the 'NominalValue' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->NominalValue, arg, db ); break; } + try { GenericConvert( in->NominalValue, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcPropertySingleValue to be a `IfcValue`")); } } while(0); do { // convert the 'Unit' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Unit, arg, db ); break; } + try { GenericConvert( in->Unit, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcPropertySingleValue to be a `IfcUnit`")); } } while(0); return base; @@ -3111,7 +3111,7 @@ template <> size_t GenericFill(const DB& db, const LIST& if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcSurfaceStyleShading"); } do { // convert the 'SurfaceColour' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->SurfaceColour, arg, db ); break; } + try { GenericConvert( in->SurfaceColour, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSurfaceStyleShading to be a `IfcColourRgb`")); } } while(0); return base; diff --git a/code/AssetLib/IFC/IFCReaderGen2_2x3.cpp b/code/AssetLib/IFC/IFCReaderGen2_2x3.cpp index c58c7c42f..0d7051195 100644 --- a/code/AssetLib/IFC/IFCReaderGen2_2x3.cpp +++ b/code/AssetLib/IFC/IFCReaderGen2_2x3.cpp @@ -5,8 +5,8 @@ Open Asset Import Library (ASSIMP) Copyright (c) 2006-2020, ASSIMP Development Team All rights reserved. -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the +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 @@ -23,16 +23,16 @@ following conditions are met: derived from this software without specific prior written permission of the ASSIMP Development Team. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +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 +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 +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 +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. ---------------------------------------------------------------------- @@ -59,12 +59,12 @@ template <> size_t GenericFill(const DB& db, const LIST& params size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcSurfaceStyle"); } do { // convert the 'Side' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Side, arg, db ); break; } + try { GenericConvert( in->Side, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSurfaceStyle to be a `IfcSurfaceSide`")); } } while(0); do { // convert the 'Styles' argument std::shared_ptr arg = params[ base++ ]; - try { GenericConvert( in->Styles, arg, db ); break; } + try { GenericConvert( in->Styles, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSurfaceStyle to be a `SET [1:5] OF IfcSurfaceStyleElementSelect`")); } } while(0); return base; @@ -118,7 +118,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcFac if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcFace"); } do { // convert the 'Bounds' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Bounds, arg, db ); break; } + try { GenericConvert( in->Bounds, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcFace to be a `SET [1:?] OF IfcFaceBound`")); } } while(0); return base; @@ -173,7 +173,7 @@ template <> size_t GenericFill(const DB& db, const LIST& std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Name, arg, db ); break; } + try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcColourSpecification to be a `IfcLabel`")); } } while(0); return base; @@ -184,12 +184,12 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcV size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcVector"); } do { // convert the 'Orientation' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Orientation, arg, db ); break; } + try { GenericConvert( in->Orientation, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcVector to be a `IfcDirection`")); } } while(0); do { // convert the 'Magnitude' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Magnitude, arg, db ); break; } + try { GenericConvert( in->Magnitude, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcVector to be a `IfcLengthMeasure`")); } } while(0); return base; @@ -207,17 +207,17 @@ template <> size_t GenericFill(const DB& db, const LIST& params, I size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcColourRgb"); } do { // convert the 'Red' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Red, arg, db ); break; } + try { GenericConvert( in->Red, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcColourRgb to be a `IfcNormalisedRatioMeasure`")); } } while(0); do { // convert the 'Green' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Green, arg, db ); break; } + try { GenericConvert( in->Green, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcColourRgb to be a `IfcNormalisedRatioMeasure`")); } } while(0); do { // convert the 'Blue' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Blue, arg, db ); break; } + try { GenericConvert( in->Blue, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcColourRgb to be a `IfcNormalisedRatioMeasure`")); } } while(0); return base; @@ -243,31 +243,31 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcSit if (params.GetSize() < 14) { throw STEP::TypeError("expected 14 arguments to IfcSite"); } do { // convert the 'RefLatitude' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->RefLatitude, arg, db ); break; } + try { GenericConvert( in->RefLatitude, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcSite to be a `IfcCompoundPlaneAngleMeasure`")); } } while(0); do { // convert the 'RefLongitude' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->RefLongitude, arg, db ); break; } + try { GenericConvert( in->RefLongitude, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 10 to IfcSite to be a `IfcCompoundPlaneAngleMeasure`")); } } while(0); do { // convert the 'RefElevation' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->RefElevation, arg, db ); break; } + try { GenericConvert( in->RefElevation, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 11 to IfcSite to be a `IfcLengthMeasure`")); } } while(0); do { // convert the 'LandTitleNumber' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->LandTitleNumber, arg, db ); break; } + try { GenericConvert( in->LandTitleNumber, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 12 to IfcSite to be a `IfcLabel`")); } } while(0); do { // convert the 'SiteAddress' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->SiteAddress, arg, db ); break; } + try { GenericConvert( in->SiteAddress, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 13 to IfcSite to be a `IfcPostalAddress`")); } } while(0); return base; @@ -412,31 +412,31 @@ template <> size_t GenericFill(const DB& db, const LIST& params if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcBSplineCurve"); } do { // convert the 'Degree' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Degree, arg, db ); break; } + try { GenericConvert( in->Degree, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcBSplineCurve to be a `INTEGER`")); } } while(0); do { // convert the 'ControlPointsList' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->ControlPointsList, arg, db ); break; } + try { GenericConvert( in->ControlPointsList, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcBSplineCurve to be a `LIST [2:?] OF IfcCartesianPoint`")); } } while(0); do { // convert the 'CurveForm' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } - try { GenericConvert( in->CurveForm, arg, db ); break; } + try { GenericConvert( in->CurveForm, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcBSplineCurve to be a `IfcBSplineCurveForm`")); } } while(0); do { // convert the 'ClosedCurve' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } - try { GenericConvert( in->ClosedCurve, arg, db ); break; } + try { GenericConvert( in->ClosedCurve, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcBSplineCurve to be a `LOGICAL`")); } } while(0); do { // convert the 'SelfIntersect' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[4]=true; break; } - try { GenericConvert( in->SelfIntersect, arg, db ); break; } + try { GenericConvert( in->SelfIntersect, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcBSplineCurve to be a `LOGICAL`")); } } while(0); return base; @@ -474,7 +474,7 @@ template <> size_t GenericFill(const DB& db, const LI size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcShellBasedSurfaceModel"); } do { // convert the 'SbsmBoundary' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->SbsmBoundary, arg, db ); break; } + try { GenericConvert( in->SbsmBoundary, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcShellBasedSurfaceModel to be a `SET [1:?] OF IfcShell`")); } } while(0); return base; @@ -492,12 +492,12 @@ template <> size_t GenericFill(const DB& db, const LIST& p size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcExtrudedAreaSolid"); } do { // convert the 'ExtrudedDirection' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->ExtrudedDirection, arg, db ); break; } + try { GenericConvert( in->ExtrudedDirection, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcExtrudedAreaSolid to be a `IfcDirection`")); } } while(0); do { // convert the 'Depth' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Depth, arg, db ); break; } + try { GenericConvert( in->Depth, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcExtrudedAreaSolid to be a `IfcPositiveLengthMeasure`")); } } while(0); return base; @@ -522,12 +522,12 @@ template <> size_t GenericFill(const DB& db, const LIST& par size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelVoidsElement"); } do { // convert the 'RelatingBuildingElement' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->RelatingBuildingElement, arg, db ); break; } + try { GenericConvert( in->RelatingBuildingElement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelVoidsElement to be a `IfcElement`")); } } while(0); do { // convert the 'RelatedOpeningElement' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->RelatedOpeningElement, arg, db ); break; } + try { GenericConvert( in->RelatedOpeningElement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelVoidsElement to be a `IfcFeatureElementSubtraction`")); } } while(0); return base; @@ -546,13 +546,13 @@ template <> size_t GenericFill(c if (params.GetSize() < 7) { throw STEP::TypeError("expected 7 arguments to IfcCartesianTransformationOperator3DnonUniform"); } do { // convert the 'Scale2' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Scale2, arg, db ); break; } + try { GenericConvert( in->Scale2, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcCartesianTransformationOperator3DnonUniform to be a `REAL`")); } } while(0); do { // convert the 'Scale3' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Scale3, arg, db ); break; } + try { GenericConvert( in->Scale3, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcCartesianTransformationOperator3DnonUniform to be a `REAL`")); } } while(0); return base; @@ -634,7 +634,7 @@ template <> size_t GenericFill(const DB& db, const LIST& pa if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcAxis2Placement2D"); } do { // convert the 'RefDirection' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->RefDirection, arg, db ); break; } + try { GenericConvert( in->RefDirection, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcAxis2Placement2D to be a `IfcDirection`")); } } while(0); return base; @@ -658,7 +658,7 @@ template <> size_t GenericFill(const DB& db, const LIST& para size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcCartesianPoint"); } do { // convert the 'Coordinates' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Coordinates, arg, db ); break; } + try { GenericConvert( in->Coordinates, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCartesianPoint to be a `LIST [1:3] OF IfcLengthMeasure`")); } } while(0); return base; @@ -682,7 +682,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, If size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPolyLoop"); } do { // convert the 'Polygon' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Polygon, arg, db ); break; } + try { GenericConvert( in->Polygon, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPolyLoop to be a `LIST [3:?] OF IfcCartesianPoint`")); } } while(0); return base; @@ -716,14 +716,14 @@ template <> size_t GenericFill(const DB& db, const LIS std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ContextIdentifier, arg, db ); break; } + try { GenericConvert( in->ContextIdentifier, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRepresentationContext to be a `IfcLabel`")); } } while(0); do { // convert the 'ContextType' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ContextType, arg, db ); break; } + try { GenericConvert( in->ContextType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRepresentationContext to be a `IfcLabel`")); } } while(0); return base; @@ -735,27 +735,27 @@ template <> size_t GenericFill(const DB& db, if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcGeometricRepresentationContext"); } do { // convert the 'CoordinateSpaceDimension' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->CoordinateSpaceDimension, arg, db ); break; } + try { GenericConvert( in->CoordinateSpaceDimension, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcGeometricRepresentationContext to be a `IfcDimensionCount`")); } } while(0); do { // convert the 'Precision' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Precision, arg, db ); break; } + try { GenericConvert( in->Precision, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcGeometricRepresentationContext to be a `REAL`")); } } while(0); do { // convert the 'WorldCoordinateSystem' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } - try { GenericConvert( in->WorldCoordinateSystem, arg, db ); break; } + try { GenericConvert( in->WorldCoordinateSystem, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcGeometricRepresentationContext to be a `IfcAxis2Placement`")); } } while(0); do { // convert the 'TrueNorth' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->TrueNorth, arg, db ); break; } + try { GenericConvert( in->TrueNorth, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcGeometricRepresentationContext to be a `IfcDirection`")); } } while(0); return base; @@ -774,12 +774,12 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcS if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcSIUnit"); } do { // convert the 'Prefix' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Prefix, arg, db ); break; } + try { GenericConvert( in->Prefix, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSIUnit to be a `IfcSIPrefix`")); } } while(0); do { // convert the 'Name' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Name, arg, db ); break; } + try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcSIUnit to be a `IfcSIUnitName`")); } } while(0); return base; @@ -805,7 +805,7 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcAxis1Placement"); } do { // convert the 'Axis' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Axis, arg, db ); break; } + try { GenericConvert( in->Axis, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcAxis1Placement to be a `IfcDirection`")); } } while(0); return base; @@ -858,12 +858,12 @@ template <> size_t GenericFill(const DB& db, const LIST& p size_t base = 0; if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcRepresentationMap"); } do { // convert the 'MappingOrigin' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->MappingOrigin, arg, db ); break; } + try { GenericConvert( in->MappingOrigin, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRepresentationMap to be a `IfcAxis2Placement`")); } } while(0); do { // convert the 'MappedRepresentation' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->MappedRepresentation, arg, db ); break; } + try { GenericConvert( in->MappedRepresentation, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRepresentationMap to be a `IfcRepresentation`")); } } while(0); return base; @@ -1012,12 +1012,12 @@ template <> size_t GenericFill(const DB& db, const LIST& par size_t base = 0; if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcMeasureWithUnit"); } do { // convert the 'ValueComponent' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->ValueComponent, arg, db ); break; } + try { GenericConvert( in->ValueComponent, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcMeasureWithUnit to be a `IfcValue`")); } } while(0); do { // convert the 'UnitComponent' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->UnitComponent, arg, db ); break; } + try { GenericConvert( in->UnitComponent, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcMeasureWithUnit to be a `IfcUnit`")); } } while(0); return base; @@ -1125,7 +1125,7 @@ template <> size_t GenericFill(const DB& db, const LIS size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcFaceBasedSurfaceModel"); } do { // convert the 'FbsmFaces' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->FbsmFaces, arg, db ); break; } + try { GenericConvert( in->FbsmFaces, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcFaceBasedSurfaceModel to be a `SET [1:?] OF IfcConnectedFaceSet`")); } } while(0); return base; @@ -1172,13 +1172,13 @@ template <> size_t GenericFill(const DB& db, const LIST& params, I if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcFaceBound"); } do { // convert the 'Bound' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Bound, arg, db ); break; } + try { GenericConvert( in->Bound, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcFaceBound to be a `IfcLoop`")); } } while(0); do { // convert the 'Orientation' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->Orientation, arg, db ); break; } + try { GenericConvert( in->Orientation, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcFaceBound to be a `BOOLEAN`")); } } while(0); return base; @@ -1216,12 +1216,12 @@ template <> size_t GenericFill(const DB& db, const LIST& par size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcComplexProperty"); } do { // convert the 'UsageName' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->UsageName, arg, db ); break; } + try { GenericConvert( in->UsageName, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcComplexProperty to be a `IfcIdentifier`")); } } while(0); do { // convert the 'HasProperties' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->HasProperties, arg, db ); break; } + try { GenericConvert( in->HasProperties, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcComplexProperty to be a `SET [1:?] OF IfcProperty`")); } } while(0); return base; @@ -1274,7 +1274,7 @@ template <> size_t GenericFill(const DB& db, const LIST& para size_t base = 0; if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcUnitAssignment"); } do { // convert the 'Units' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Units, arg, db ); break; } + try { GenericConvert( in->Units, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcUnitAssignment to be a `SET [1:?] OF IfcUnit`")); } } while(0); return base; @@ -1307,12 +1307,12 @@ template <> size_t GenericFill(const DB& db, const LIST& par if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcElementQuantity"); } do { // convert the 'MethodOfMeasurement' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->MethodOfMeasurement, arg, db ); break; } + try { GenericConvert( in->MethodOfMeasurement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcElementQuantity to be a `IfcLabel`")); } } while(0); do { // convert the 'Quantities' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Quantities, arg, db ); break; } + try { GenericConvert( in->Quantities, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcElementQuantity to be a `SET [1:?] OF IfcPhysicalQuantity`")); } } while(0); return base; @@ -1379,7 +1379,7 @@ template <> size_t GenericFill(const DB& db, con size_t base = 0; if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPresentationStyleAssignment"); } do { // convert the 'Styles' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Styles, arg, db ); break; } + try { GenericConvert( in->Styles, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPresentationStyleAssignment to be a `SET [1:?] OF IfcPresentationStyleSelect`")); } } while(0); return base; @@ -1418,13 +1418,13 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcSp size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 11) { throw STEP::TypeError("expected 11 arguments to IfcSpace"); } do { // convert the 'InteriorOrExteriorSpace' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->InteriorOrExteriorSpace, arg, db ); break; } + try { GenericConvert( in->InteriorOrExteriorSpace, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcSpace to be a `IfcInternalOrExternalEnum`")); } } while(0); do { // convert the 'ElevationWithFlooring' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ElevationWithFlooring, arg, db ); break; } + try { GenericConvert( in->ElevationWithFlooring, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 10 to IfcSpace to be a `IfcLengthMeasure`")); } } while(0); return base; @@ -1484,7 +1484,7 @@ template <> size_t GenericFill(const DB& db, const size_t base = 0; if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcSurfaceStyleWithTextures"); } do { // convert the 'Textures' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Textures, arg, db ); break; } + try { GenericConvert( in->Textures, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSurfaceStyleWithTextures to be a `LIST [1:?] OF IfcSurfaceTexture`")); } } while(0); return base; @@ -1495,22 +1495,22 @@ template <> size_t GenericFill(const DB& db, const LIST& params, size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcBoundingBox"); } do { // convert the 'Corner' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Corner, arg, db ); break; } + try { GenericConvert( in->Corner, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcBoundingBox to be a `IfcCartesianPoint`")); } } while(0); do { // convert the 'XDim' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->XDim, arg, db ); break; } + try { GenericConvert( in->XDim, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcBoundingBox to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'YDim' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->YDim, arg, db ); break; } + try { GenericConvert( in->YDim, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcBoundingBox to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'ZDim' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->ZDim, arg, db ); break; } + try { GenericConvert( in->ZDim, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcBoundingBox to be a `IfcPositiveLengthMeasure`")); } } while(0); return base; @@ -1535,7 +1535,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcC size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcCircle"); } do { // convert the 'Radius' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Radius, arg, db ); break; } + try { GenericConvert( in->Radius, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCircle to be a `IfcPositiveLengthMeasure`")); } } while(0); return base; @@ -1623,12 +1623,12 @@ template <> size_t GenericFill(const DB& db, const LIST& size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcConversionBasedUnit"); } do { // convert the 'Name' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->Name, arg, db ); break; } + try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcConversionBasedUnit to be a `IfcLabel`")); } } while(0); do { // convert the 'ConversionFactor' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->ConversionFactor, arg, db ); break; } + try { GenericConvert( in->ConversionFactor, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcConversionBasedUnit to be a `IfcMeasureWithUnit`")); } } while(0); return base; @@ -1744,12 +1744,12 @@ template <> size_t GenericFill(const DB& db, const LIST& params, Ifc size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcEllipse"); } do { // convert the 'SemiAxis1' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->SemiAxis1, arg, db ); break; } + try { GenericConvert( in->SemiAxis1, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcEllipse to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'SemiAxis2' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->SemiAxis2, arg, db ); break; } + try { GenericConvert( in->SemiAxis2, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcEllipse to be a `IfcPositiveLengthMeasure`")); } } while(0); return base; @@ -1816,7 +1816,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcPropertySet"); } do { // convert the 'HasProperties' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->HasProperties, arg, db ); break; } + try { GenericConvert( in->HasProperties, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcPropertySet to be a `SET [1:?] OF IfcProperty`")); } } while(0); return base; @@ -1828,48 +1828,48 @@ template <> size_t GenericFill(const DB& db, const LIS if (params.GetSize() < 9) { throw STEP::TypeError("expected 9 arguments to IfcSurfaceStyleRendering"); } do { // convert the 'Transparency' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Transparency, arg, db ); break; } + try { GenericConvert( in->Transparency, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSurfaceStyleRendering to be a `IfcNormalisedRatioMeasure`")); } } while(0); do { // convert the 'DiffuseColour' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->DiffuseColour, arg, db ); break; } + try { GenericConvert( in->DiffuseColour, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } } while(0); do { // convert the 'TransmissionColour' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->TransmissionColour, arg, db ); break; } + try { GenericConvert( in->TransmissionColour, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } } while(0); do { // convert the 'DiffuseTransmissionColour' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->DiffuseTransmissionColour, arg, db ); break; } + try { GenericConvert( in->DiffuseTransmissionColour, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } } while(0); do { // convert the 'ReflectionColour' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ReflectionColour, arg, db ); break; } + try { GenericConvert( in->ReflectionColour, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } } while(0); do { // convert the 'SpecularColour' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->SpecularColour, arg, db ); break; } + try { GenericConvert( in->SpecularColour, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } } while(0); do { // convert the 'SpecularHighlight' argument std::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->SpecularHighlight, arg, db ); break; } + try { GenericConvert( in->SpecularHighlight, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcSurfaceStyleRendering to be a `IfcSpecularHighlightSelect`")); } } while(0); do { // convert the 'ReflectanceMethod' argument std::shared_ptr arg = params[base++]; - try { GenericConvert( in->ReflectanceMethod, arg, db ); break; } + try { GenericConvert( in->ReflectanceMethod, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcSurfaceStyleRendering to be a `IfcReflectanceMethodEnum`")); } } while(0); return base; diff --git a/code/AssetLib/IFC/IFCReaderGen_4.cpp b/code/AssetLib/IFC/IFCReaderGen_4.cpp index 9eb3e2446..179322902 100644 --- a/code/AssetLib/IFC/IFCReaderGen_4.cpp +++ b/code/AssetLib/IFC/IFCReaderGen_4.cpp @@ -5,8 +5,8 @@ Open Asset Import Library (ASSIMP) Copyright (c) 2006-2020, ASSIMP Development Team All rights reserved. -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the +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 @@ -23,16 +23,16 @@ following conditions are met: derived from this software without specific prior written permission of the ASSIMP Development Team. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +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 +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 +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 +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. ---------------------------------------------------------------------- @@ -1254,28 +1254,28 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcRoo if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcRoot"); } do { // convert the 'GlobalId' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->GlobalId, arg, db ); break; } + try { GenericConvert( in->GlobalId, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRoot to be a `IfcGloballyUniqueId`")); } } while(0); do { // convert the 'OwnerHistory' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->OwnerHistory, arg, db ); break; } + try { GenericConvert( in->OwnerHistory, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRoot to be a `IfcOwnerHistory`")); } } while(0); do { // convert the 'Name' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Name, arg, db ); break; } + try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcRoot to be a `IfcLabel`")); } } while(0); do { // convert the 'Description' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Description, arg, db ); break; } + try { GenericConvert( in->Description, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRoot to be a `IfcText`")); } } while(0); return base; @@ -1294,7 +1294,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcO boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ObjectType, arg, db ); break; } + try { GenericConvert( in->ObjectType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcObject to be a `IfcLabel`")); } } while(0); return base; @@ -1328,14 +1328,14 @@ template <> size_t GenericFill(const DB& db, const LIST& params, Ifc boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ObjectPlacement, arg, db ); break; } + try { GenericConvert( in->ObjectPlacement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcProduct to be a `IfcObjectPlacement`")); } } while(0); do { // convert the 'Representation' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Representation, arg, db ); break; } + try { GenericConvert( in->Representation, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcProduct to be a `IfcProductRepresentation`")); } } while(0); return base; @@ -1348,7 +1348,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, Ifc boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Tag, arg, db ); break; } + try { GenericConvert( in->Tag, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcElement to be a `IfcIdentifier`")); } } while(0); return base; @@ -1441,7 +1441,7 @@ template <> size_t GenericFill(const DB& db, const LIST& p if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcManifoldSolidBrep"); } do { // convert the 'Outer' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Outer, arg, db ); break; } + try { GenericConvert( in->Outer, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcManifoldSolidBrep to be a `IfcClosedShell`")); } } while(0); return base; @@ -1473,7 +1473,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcFac if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcFace"); } do { // convert the 'Bounds' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Bounds, arg, db ); break; } + try { GenericConvert( in->Bounds, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcFace to be a `SET [1:?] OF IfcFaceBound`")); } } while(0); return base; @@ -1624,14 +1624,14 @@ template <> size_t GenericFill(const DB& db, const LIST& params, if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcProfileDef"); } do { // convert the 'ProfileType' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->ProfileType, arg, db ); break; } + try { GenericConvert( in->ProfileType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcProfileDef to be a `IfcProfileTypeEnum`")); } } while(0); do { // convert the 'ProfileName' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ProfileName, arg, db ); break; } + try { GenericConvert( in->ProfileName, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcProfileDef to be a `IfcLabel`")); } } while(0); return base; @@ -1643,7 +1643,7 @@ template <> size_t GenericFill(const DB& db, const if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcArbitraryClosedProfileDef"); } do { // convert the 'OuterCurve' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->OuterCurve, arg, db ); break; } + try { GenericConvert( in->OuterCurve, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcArbitraryClosedProfileDef to be a `IfcCurve`")); } } while(0); return base; @@ -1655,7 +1655,7 @@ template <> size_t GenericFill(const DB& db, const L if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcArbitraryOpenProfileDef"); } do { // convert the 'Curve' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Curve, arg, db ); break; } + try { GenericConvert( in->Curve, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcArbitraryOpenProfileDef to be a `IfcBoundedCurve`")); } } while(0); return base; @@ -1666,7 +1666,7 @@ template <> size_t GenericFill(const DB& db, co size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcArbitraryProfileDefWithVoids"); } do { // convert the 'InnerCurves' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->InnerCurves, arg, db ); break; } + try { GenericConvert( in->InnerCurves, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcArbitraryProfileDefWithVoids to be a `SET [1:?] OF IfcCurve`")); } } while(0); return base; @@ -1693,7 +1693,7 @@ template <> size_t GenericFill(const DB& db, const L boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Position, arg, db ); break; } + try { GenericConvert( in->Position, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcParameterizedProfileDef to be a `IfcAxis2Placement2D`")); } } while(0); return base; @@ -1726,7 +1726,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, I if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPlacement"); } do { // convert the 'Location' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Location, arg, db ); break; } + try { GenericConvert( in->Location, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPlacement to be a `IfcCartesianPoint`")); } } while(0); return base; @@ -1738,7 +1738,7 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcAxis1Placement"); } do { // convert the 'Axis' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Axis, arg, db ); break; } + try { GenericConvert( in->Axis, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcAxis1Placement to be a `IfcDirection`")); } } while(0); return base; @@ -1750,7 +1750,7 @@ template <> size_t GenericFill(const DB& db, const LIST& pa if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcAxis2Placement2D"); } do { // convert the 'RefDirection' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->RefDirection, arg, db ); break; } + try { GenericConvert( in->RefDirection, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcAxis2Placement2D to be a `IfcDirection`")); } } while(0); return base; @@ -1762,13 +1762,13 @@ template <> size_t GenericFill(const DB& db, const LIST& pa if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcAxis2Placement3D"); } do { // convert the 'Axis' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Axis, arg, db ); break; } + try { GenericConvert( in->Axis, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcAxis2Placement3D to be a `IfcDirection`")); } } while(0); do { // convert the 'RefDirection' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->RefDirection, arg, db ); break; } + try { GenericConvert( in->RefDirection, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcAxis2Placement3D to be a `IfcDirection`")); } } while(0); return base; @@ -1792,31 +1792,31 @@ template <> size_t GenericFill(const DB& db, const LIST& params if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcBSplineCurve"); } do { // convert the 'Degree' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Degree, arg, db ); break; } + try { GenericConvert( in->Degree, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcBSplineCurve to be a `IfcInteger`")); } } while(0); do { // convert the 'ControlPointsList' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->ControlPointsList, arg, db ); break; } + try { GenericConvert( in->ControlPointsList, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcBSplineCurve to be a `LIST [2:?] OF IfcCartesianPoint`")); } } while(0); do { // convert the 'CurveForm' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } - try { GenericConvert( in->CurveForm, arg, db ); break; } + try { GenericConvert( in->CurveForm, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcBSplineCurve to be a `IfcBSplineCurveForm`")); } } while(0); do { // convert the 'ClosedCurve' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } - try { GenericConvert( in->ClosedCurve, arg, db ); break; } + try { GenericConvert( in->ClosedCurve, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcBSplineCurve to be a `IfcLogical`")); } } while(0); do { // convert the 'SelfIntersect' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[4]=true; break; } - try { GenericConvert( in->SelfIntersect, arg, db ); break; } + try { GenericConvert( in->SelfIntersect, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcBSplineCurve to be a `IfcLogical`")); } } while(0); return base; @@ -1930,19 +1930,19 @@ template <> size_t GenericFill(const DB& db, const LIST& param if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcBooleanResult"); } do { // convert the 'Operator' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Operator, arg, db ); break; } + try { GenericConvert( in->Operator, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcBooleanResult to be a `IfcBooleanOperator`")); } } while(0); do { // convert the 'FirstOperand' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->FirstOperand, arg, db ); break; } + try { GenericConvert( in->FirstOperand, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcBooleanResult to be a `IfcBooleanOperand`")); } } while(0); do { // convert the 'SecondOperand' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } - try { GenericConvert( in->SecondOperand, arg, db ); break; } + try { GenericConvert( in->SecondOperand, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcBooleanResult to be a `IfcBooleanOperand`")); } } while(0); return base; @@ -1960,13 +1960,13 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcCompositeCurve"); } do { // convert the 'Segments' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Segments, arg, db ); break; } + try { GenericConvert( in->Segments, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCompositeCurve to be a `LIST [1:?] OF IfcCompositeCurveSegment`")); } } while(0); do { // convert the 'SelfIntersect' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->SelfIntersect, arg, db ); break; } + try { GenericConvert( in->SelfIntersect, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCompositeCurve to be a `IfcLogical`")); } } while(0); return base; @@ -1991,22 +1991,22 @@ template <> size_t GenericFill(const DB& db, const LIST& params, size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcBoundingBox"); } do { // convert the 'Corner' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Corner, arg, db ); break; } + try { GenericConvert( in->Corner, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcBoundingBox to be a `IfcCartesianPoint`")); } } while(0); do { // convert the 'XDim' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->XDim, arg, db ); break; } + try { GenericConvert( in->XDim, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcBoundingBox to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'YDim' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->YDim, arg, db ); break; } + try { GenericConvert( in->YDim, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcBoundingBox to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'ZDim' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->ZDim, arg, db ); break; } + try { GenericConvert( in->ZDim, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcBoundingBox to be a `IfcPositiveLengthMeasure`")); } } while(0); return base; @@ -2018,13 +2018,13 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcHalfSpaceSolid"); } do { // convert the 'BaseSurface' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->BaseSurface, arg, db ); break; } + try { GenericConvert( in->BaseSurface, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcHalfSpaceSolid to be a `IfcSurface`")); } } while(0); do { // convert the 'AgreementFlag' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->AgreementFlag, arg, db ); break; } + try { GenericConvert( in->AgreementFlag, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcHalfSpaceSolid to be a `IfcBoolean`")); } } while(0); return base; @@ -2044,7 +2044,7 @@ template <> size_t GenericFill(const DB& db, const LIST& para boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->LongName, arg, db ); break; } + try { GenericConvert( in->LongName, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcSpatialElement to be a `IfcLabel`")); } } while(0); return base; @@ -2057,7 +2057,7 @@ template <> size_t GenericFill(const DB& db, const L boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->CompositionType, arg, db ); break; } + try { GenericConvert( in->CompositionType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcSpatialStructureElement to be a `IfcElementCompositionEnum`")); } } while(0); return base; @@ -2069,19 +2069,19 @@ template <> size_t GenericFill(const DB& db, const LIST& params, If if (params.GetSize() < 12) { throw STEP::TypeError("expected 12 arguments to IfcBuilding"); } do { // convert the 'ElevationOfRefHeight' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ElevationOfRefHeight, arg, db ); break; } + try { GenericConvert( in->ElevationOfRefHeight, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcBuilding to be a `IfcLengthMeasure`")); } } while(0); do { // convert the 'ElevationOfTerrain' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ElevationOfTerrain, arg, db ); break; } + try { GenericConvert( in->ElevationOfTerrain, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 10 to IfcBuilding to be a `IfcLengthMeasure`")); } } while(0); do { // convert the 'BuildingAddress' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->BuildingAddress, arg, db ); break; } + try { GenericConvert( in->BuildingAddress, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 11 to IfcBuilding to be a `IfcPostalAddress`")); } } while(0); return base; @@ -2266,7 +2266,7 @@ template <> size_t GenericFill(const DB& db, const LIST& para size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcCartesianPoint"); } do { // convert the 'Coordinates' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Coordinates, arg, db ); break; } + try { GenericConvert( in->Coordinates, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCartesianPoint to be a `LIST [1:3] OF IfcLengthMeasure`")); } } while(0); return base; @@ -2300,27 +2300,27 @@ template <> size_t GenericFill(const DB& db, boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Axis1, arg, db ); break; } + try { GenericConvert( in->Axis1, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCartesianTransformationOperator to be a `IfcDirection`")); } } while(0); do { // convert the 'Axis2' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Axis2, arg, db ); break; } + try { GenericConvert( in->Axis2, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCartesianTransformationOperator to be a `IfcDirection`")); } } while(0); do { // convert the 'LocalOrigin' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } - try { GenericConvert( in->LocalOrigin, arg, db ); break; } + try { GenericConvert( in->LocalOrigin, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcCartesianTransformationOperator to be a `IfcCartesianPoint`")); } } while(0); do { // convert the 'Scale' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Scale, arg, db ); break; } + try { GenericConvert( in->Scale, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcCartesianTransformationOperator to be a `IfcReal`")); } } while(0); return base; @@ -2347,7 +2347,7 @@ template <> size_t GenericFill(const DB& d boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Axis3, arg, db ); break; } + try { GenericConvert( in->Axis3, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcCartesianTransformationOperator3D to be a `IfcDirection`")); } } while(0); return base; @@ -2359,13 +2359,13 @@ template <> size_t GenericFill(c if (params.GetSize() < 7) { throw STEP::TypeError("expected 7 arguments to IfcCartesianTransformationOperator3DnonUniform"); } do { // convert the 'Scale2' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Scale2, arg, db ); break; } + try { GenericConvert( in->Scale2, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcCartesianTransformationOperator3DnonUniform to be a `IfcReal`")); } } while(0); do { // convert the 'Scale3' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Scale3, arg, db ); break; } + try { GenericConvert( in->Scale3, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcCartesianTransformationOperator3DnonUniform to be a `IfcReal`")); } } while(0); return base; @@ -2412,7 +2412,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcCo if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcConic"); } do { // convert the 'Position' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Position, arg, db ); break; } + try { GenericConvert( in->Position, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcConic to be a `IfcAxis2Placement`")); } } while(0); return base; @@ -2423,7 +2423,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcC size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcCircle"); } do { // convert the 'Radius' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Radius, arg, db ); break; } + try { GenericConvert( in->Radius, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCircle to be a `IfcPositiveLengthMeasure`")); } } while(0); return base; @@ -2435,7 +2435,7 @@ template <> size_t GenericFill(const DB& db, const LIST& pa if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcCircleProfileDef"); } do { // convert the 'Radius' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Radius, arg, db ); break; } + try { GenericConvert( in->Radius, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcCircleProfileDef to be a `IfcPositiveLengthMeasure`")); } } while(0); return base; @@ -2446,7 +2446,7 @@ template <> size_t GenericFill(const DB& db, const LI size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcCircleHollowProfileDef"); } do { // convert the 'WallThickness' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->WallThickness, arg, db ); break; } + try { GenericConvert( in->WallThickness, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcCircleHollowProfileDef to be a `IfcPositiveLengthMeasure`")); } } while(0); return base; @@ -2472,7 +2472,7 @@ template <> size_t GenericFill(const DB& db, const LIST& pa if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcConnectedFaceSet"); } do { // convert the 'CfsFaces' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->CfsFaces, arg, db ); break; } + try { GenericConvert( in->CfsFaces, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcConnectedFaceSet to be a `SET [1:?] OF IfcFace`")); } } while(0); return base; @@ -2505,7 +2505,7 @@ template <> size_t GenericFill(const DB& db, const LIST& boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Name, arg, db ); break; } + try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcColourSpecification to be a `IfcLabel`")); } } while(0); return base; @@ -2516,17 +2516,17 @@ template <> size_t GenericFill(const DB& db, const LIST& params, I size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcColourRgb"); } do { // convert the 'Red' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Red, arg, db ); break; } + try { GenericConvert( in->Red, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcColourRgb to be a `IfcNormalisedRatioMeasure`")); } } while(0); do { // convert the 'Green' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Green, arg, db ); break; } + try { GenericConvert( in->Green, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcColourRgb to be a `IfcNormalisedRatioMeasure`")); } } while(0); do { // convert the 'Blue' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Blue, arg, db ); break; } + try { GenericConvert( in->Blue, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcColourRgb to be a `IfcNormalisedRatioMeasure`")); } } while(0); return base; @@ -2579,14 +2579,14 @@ template <> size_t GenericFill(const DB& db, const LIST& params, If if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcProperty"); } do { // convert the 'Name' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Name, arg, db ); break; } + try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcProperty to be a `IfcIdentifier`")); } } while(0); do { // convert the 'Description' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Description, arg, db ); break; } + try { GenericConvert( in->Description, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcProperty to be a `IfcText`")); } } while(0); return base; @@ -2597,12 +2597,12 @@ template <> size_t GenericFill(const DB& db, const LIST& par size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcComplexProperty"); } do { // convert the 'UsageName' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->UsageName, arg, db ); break; } + try { GenericConvert( in->UsageName, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcComplexProperty to be a `IfcIdentifier`")); } } while(0); do { // convert the 'HasProperties' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->HasProperties, arg, db ); break; } + try { GenericConvert( in->HasProperties, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcComplexProperty to be a `SET [1:?] OF IfcProperty`")); } } while(0); return base; @@ -2620,19 +2620,19 @@ template <> size_t GenericFill(const DB& db, const LIS if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcCompositeCurveSegment"); } do { // convert the 'Transition' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Transition, arg, db ); break; } + try { GenericConvert( in->Transition, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcCompositeCurveSegment to be a `IfcTransitionCode`")); } } while(0); do { // convert the 'SameSense' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->SameSense, arg, db ); break; } + try { GenericConvert( in->SameSense, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcCompositeCurveSegment to be a `IfcBoolean`")); } } while(0); do { // convert the 'ParentCurve' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } - try { GenericConvert( in->ParentCurve, arg, db ); break; } + try { GenericConvert( in->ParentCurve, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcCompositeCurveSegment to be a `IfcCurve`")); } } while(0); return base; @@ -2764,35 +2764,35 @@ template <> size_t GenericFill(const DB& db, const LIST& params, Ifc boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ObjectType, arg, db ); break; } + try { GenericConvert( in->ObjectType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcContext to be a `IfcLabel`")); } } while(0); do { // convert the 'LongName' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->LongName, arg, db ); break; } + try { GenericConvert( in->LongName, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcContext to be a `IfcLabel`")); } } while(0); do { // convert the 'Phase' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Phase, arg, db ); break; } + try { GenericConvert( in->Phase, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcContext to be a `IfcLabel`")); } } while(0); do { // convert the 'RepresentationContexts' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->RepresentationContexts, arg, db ); break; } + try { GenericConvert( in->RepresentationContexts, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcContext to be a `SET [1:?] OF IfcRepresentationContext`")); } } while(0); do { // convert the 'UnitsInContext' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[4]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->UnitsInContext, arg, db ); break; } + try { GenericConvert( in->UnitsInContext, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcContext to be a `IfcUnitAssignment`")); } } while(0); return base; @@ -2804,13 +2804,13 @@ template <> size_t GenericFill(const DB& db, const LIST& params, I if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcNamedUnit"); } do { // convert the 'Dimensions' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Dimensions, arg, db ); break; } + try { GenericConvert( in->Dimensions, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcNamedUnit to be a `IfcDimensionalExponents`")); } } while(0); do { // convert the 'UnitType' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->UnitType, arg, db ); break; } + try { GenericConvert( in->UnitType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcNamedUnit to be a `IfcUnitEnum`")); } } while(0); return base; @@ -2843,13 +2843,13 @@ template <> size_t GenericFill(const DB& db, const LIST& if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcConversionBasedUnit"); } do { // convert the 'Name' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Name, arg, db ); break; } + try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcConversionBasedUnit to be a `IfcLabel`")); } } while(0); do { // convert the 'ConversionFactor' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->ConversionFactor, arg, db ); break; } + try { GenericConvert( in->ConversionFactor, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcConversionBasedUnit to be a `IfcMeasureWithUnit`")); } } while(0); return base; @@ -2974,7 +2974,7 @@ template <> size_t GenericFill(const DB& db, const LIST& p boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Name, arg, db ); break; } + try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPresentationStyle to be a `IfcLabel`")); } } while(0); return base; @@ -2986,7 +2986,7 @@ template <> size_t GenericFill(const DB& db, const LIST& p if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcElementarySurface"); } do { // convert the 'Position' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Position, arg, db ); break; } + try { GenericConvert( in->Position, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcElementarySurface to be a `IfcAxis2Placement3D`")); } } while(0); return base; @@ -3025,7 +3025,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, I size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcDirection"); } do { // convert the 'DirectionRatios' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->DirectionRatios, arg, db ); break; } + try { GenericConvert( in->DirectionRatios, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcDirection to be a `LIST [2:3] OF IfcReal`")); } } while(0); return base; @@ -3094,35 +3094,35 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcDoo boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->OverallHeight, arg, db ); break; } + try { GenericConvert( in->OverallHeight, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcDoor to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'OverallWidth' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->OverallWidth, arg, db ); break; } + try { GenericConvert( in->OverallWidth, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcDoor to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'PredefinedType' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->PredefinedType, arg, db ); break; } + try { GenericConvert( in->PredefinedType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 10 to IfcDoor to be a `IfcDoorTypeEnum`")); } } while(0); do { // convert the 'OperationType' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->OperationType, arg, db ); break; } + try { GenericConvert( in->OperationType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 11 to IfcDoor to be a `IfcDoorTypeOperationEnum`")); } } while(0); do { // convert the 'UserDefinedOperationType' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[4]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->UserDefinedOperationType, arg, db ); break; } + try { GenericConvert( in->UserDefinedOperationType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 12 to IfcDoor to be a `IfcLabel`")); } } while(0); return base; @@ -3362,12 +3362,12 @@ template <> size_t GenericFill(const DB& db, const LIST& par if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcElementQuantity"); } do { // convert the 'MethodOfMeasurement' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->MethodOfMeasurement, arg, db ); break; } + try { GenericConvert( in->MethodOfMeasurement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcElementQuantity to be a `IfcLabel`")); } } while(0); do { // convert the 'Quantities' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Quantities, arg, db ); break; } + try { GenericConvert( in->Quantities, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcElementQuantity to be a `SET [1:?] OF IfcPhysicalQuantity`")); } } while(0); return base; @@ -3378,12 +3378,12 @@ template <> size_t GenericFill(const DB& db, const LIST& params, Ifc size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcEllipse"); } do { // convert the 'SemiAxis1' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->SemiAxis1, arg, db ); break; } + try { GenericConvert( in->SemiAxis1, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcEllipse to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'SemiAxis2' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->SemiAxis2, arg, db ); break; } + try { GenericConvert( in->SemiAxis2, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcEllipse to be a `IfcPositiveLengthMeasure`")); } } while(0); return base; @@ -3486,14 +3486,14 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcSweptAreaSolid"); } do { // convert the 'SweptArea' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->SweptArea, arg, db ); break; } + try { GenericConvert( in->SweptArea, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSweptAreaSolid to be a `IfcProfileDef`")); } } while(0); do { // convert the 'Position' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Position, arg, db ); break; } + try { GenericConvert( in->Position, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSweptAreaSolid to be a `IfcAxis2Placement3D`")); } } while(0); return base; @@ -3505,13 +3505,13 @@ template <> size_t GenericFill(const DB& db, const LIST& p if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcExtrudedAreaSolid"); } do { // convert the 'ExtrudedDirection' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->ExtrudedDirection, arg, db ); break; } + try { GenericConvert( in->ExtrudedDirection, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcExtrudedAreaSolid to be a `IfcDirection`")); } } while(0); do { // convert the 'Depth' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->Depth, arg, db ); break; } + try { GenericConvert( in->Depth, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcExtrudedAreaSolid to be a `IfcPositiveLengthMeasure`")); } } while(0); return base; @@ -3529,7 +3529,7 @@ template <> size_t GenericFill(const DB& db, const LIS size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcFaceBasedSurfaceModel"); } do { // convert the 'FbsmFaces' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->FbsmFaces, arg, db ); break; } + try { GenericConvert( in->FbsmFaces, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcFaceBasedSurfaceModel to be a `SET [1:?] OF IfcConnectedFaceSet`")); } } while(0); return base; @@ -3541,13 +3541,13 @@ template <> size_t GenericFill(const DB& db, const LIST& params, I if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcFaceBound"); } do { // convert the 'Bound' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Bound, arg, db ); break; } + try { GenericConvert( in->Bound, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcFaceBound to be a `IfcLoop`")); } } while(0); do { // convert the 'Orientation' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->Orientation, arg, db ); break; } + try { GenericConvert( in->Orientation, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcFaceBound to be a `IfcBoolean`")); } } while(0); return base; @@ -3774,14 +3774,14 @@ template <> size_t GenericFill(const DB& db, const LIS boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ContextIdentifier, arg, db ); break; } + try { GenericConvert( in->ContextIdentifier, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRepresentationContext to be a `IfcLabel`")); } } while(0); do { // convert the 'ContextType' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ContextType, arg, db ); break; } + try { GenericConvert( in->ContextType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRepresentationContext to be a `IfcLabel`")); } } while(0); return base; @@ -3793,27 +3793,27 @@ template <> size_t GenericFill(const DB& db, if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcGeometricRepresentationContext"); } do { // convert the 'CoordinateSpaceDimension' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->CoordinateSpaceDimension, arg, db ); break; } + try { GenericConvert( in->CoordinateSpaceDimension, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcGeometricRepresentationContext to be a `IfcDimensionCount`")); } } while(0); do { // convert the 'Precision' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Precision, arg, db ); break; } + try { GenericConvert( in->Precision, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcGeometricRepresentationContext to be a `IfcReal`")); } } while(0); do { // convert the 'WorldCoordinateSystem' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } - try { GenericConvert( in->WorldCoordinateSystem, arg, db ); break; } + try { GenericConvert( in->WorldCoordinateSystem, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcGeometricRepresentationContext to be a `IfcAxis2Placement`")); } } while(0); do { // convert the 'TrueNorth' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->TrueNorth, arg, db ); break; } + try { GenericConvert( in->TrueNorth, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcGeometricRepresentationContext to be a `IfcDirection`")); } } while(0); return base; @@ -3879,40 +3879,40 @@ template <> size_t GenericFill(const DB& db, const LIST& pa size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 10) { throw STEP::TypeError("expected 10 arguments to IfcIShapeProfileDef"); } do { // convert the 'OverallWidth' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->OverallWidth, arg, db ); break; } + try { GenericConvert( in->OverallWidth, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'OverallDepth' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->OverallDepth, arg, db ); break; } + try { GenericConvert( in->OverallDepth, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'WebThickness' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->WebThickness, arg, db ); break; } + try { GenericConvert( in->WebThickness, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'FlangeThickness' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->FlangeThickness, arg, db ); break; } + try { GenericConvert( in->FlangeThickness, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcIShapeProfileDef to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'FilletRadius' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->FilletRadius, arg, db ); break; } + try { GenericConvert( in->FilletRadius, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcIShapeProfileDef to be a `IfcNonNegativeLengthMeasure`")); } } while(0); do { // convert the 'FlangeEdgeRadius' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->FlangeEdgeRadius, arg, db ); break; } + try { GenericConvert( in->FlangeEdgeRadius, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcIShapeProfileDef to be a `IfcNonNegativeLengthMeasure`")); } } while(0); do { // convert the 'FlangeSlope' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->FlangeSlope, arg, db ); break; } + try { GenericConvert( in->FlangeSlope, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcIShapeProfileDef to be a `IfcPlaneAngleMeasure`")); } } while(0); return base; @@ -4091,12 +4091,12 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcLin size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcLine"); } do { // convert the 'Pnt' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Pnt, arg, db ); break; } + try { GenericConvert( in->Pnt, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcLine to be a `IfcCartesianPoint`")); } } while(0); do { // convert the 'Dir' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Dir, arg, db ); break; } + try { GenericConvert( in->Dir, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcLine to be a `IfcVector`")); } } while(0); return base; @@ -4108,12 +4108,12 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcLocalPlacement"); } do { // convert the 'PlacementRelTo' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->PlacementRelTo, arg, db ); break; } + try { GenericConvert( in->PlacementRelTo, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcLocalPlacement to be a `IfcObjectPlacement`")); } } while(0); do { // convert the 'RelativePlacement' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->RelativePlacement, arg, db ); break; } + try { GenericConvert( in->RelativePlacement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcLocalPlacement to be a `IfcAxis2Placement`")); } } while(0); return base; @@ -4124,12 +4124,12 @@ template <> size_t GenericFill(const DB& db, const LIST& params, size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcMappedItem"); } do { // convert the 'MappingSource' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->MappingSource, arg, db ); break; } + try { GenericConvert( in->MappingSource, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcMappedItem to be a `IfcRepresentationMap`")); } } while(0); do { // convert the 'MappingTarget' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->MappingTarget, arg, db ); break; } + try { GenericConvert( in->MappingTarget, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcMappedItem to be a `IfcCartesianTransformationOperator`")); } } while(0); return base; @@ -4142,20 +4142,20 @@ template <> size_t GenericFill(const DB& db, const LIS boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Name, arg, db ); break; } + try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcProductRepresentation to be a `IfcLabel`")); } } while(0); do { // convert the 'Description' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Description, arg, db ); break; } + try { GenericConvert( in->Description, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcProductRepresentation to be a `IfcText`")); } } while(0); do { // convert the 'Representations' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } - try { GenericConvert( in->Representations, arg, db ); break; } + try { GenericConvert( in->Representations, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcProductRepresentation to be a `LIST [1:?] OF IfcRepresentation`")); } } while(0); return base; @@ -4173,12 +4173,12 @@ template <> size_t GenericFill(const DB& db, const LIST& par size_t base = 0; if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcMeasureWithUnit"); } do { // convert the 'ValueComponent' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->ValueComponent, arg, db ); break; } + try { GenericConvert( in->ValueComponent, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcMeasureWithUnit to be a `IfcValue`")); } } while(0); do { // convert the 'UnitComponent' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->UnitComponent, arg, db ); break; } + try { GenericConvert( in->UnitComponent, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcMeasureWithUnit to be a `IfcUnit`")); } } while(0); return base; @@ -4289,7 +4289,7 @@ template <> size_t GenericFill(const DB& db, const LIST& para boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->PredefinedType, arg, db ); break; } + try { GenericConvert( in->PredefinedType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcOpeningElement to be a `IfcOpeningElementTypeEnum`")); } } while(0); return base; @@ -4460,7 +4460,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, If size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPolyLoop"); } do { // convert the 'Polygon' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Polygon, arg, db ); break; } + try { GenericConvert( in->Polygon, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPolyLoop to be a `LIST [3:?] OF IfcCartesianPoint`")); } } while(0); return base; @@ -4471,12 +4471,12 @@ template <> size_t GenericFill(const DB& db, const size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcPolygonalBoundedHalfSpace"); } do { // convert the 'Position' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Position, arg, db ); break; } + try { GenericConvert( in->Position, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcPolygonalBoundedHalfSpace to be a `IfcAxis2Placement3D`")); } } while(0); do { // convert the 'PolygonalBoundary' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->PolygonalBoundary, arg, db ); break; } + try { GenericConvert( in->PolygonalBoundary, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcPolygonalBoundedHalfSpace to be a `IfcBoundedCurve`")); } } while(0); return base; @@ -4501,7 +4501,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, If size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPolyline"); } do { // convert the 'Points' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Points, arg, db ); break; } + try { GenericConvert( in->Points, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPolyline to be a `LIST [2:?] OF IfcCartesianPoint`")); } } while(0); return base; @@ -4512,7 +4512,7 @@ template <> size_t GenericFill(const DB& db, con size_t base = 0; if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcPresentationStyleAssignment"); } do { // convert the 'Styles' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Styles, arg, db ); break; } + try { GenericConvert( in->Styles, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcPresentationStyleAssignment to be a `SET [1:?] OF IfcPresentationStyleSelect`")); } } while(0); return base; @@ -4592,13 +4592,13 @@ template <> size_t GenericFill(const DB& db, const LIST& p if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcPropertyListValue"); } do { // convert the 'ListValues' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ListValues, arg, db ); break; } + try { GenericConvert( in->ListValues, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcPropertyListValue to be a `LIST [1:?] OF IfcValue`")); } } while(0); do { // convert the 'Unit' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Unit, arg, db ); break; } + try { GenericConvert( in->Unit, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcPropertyListValue to be a `IfcUnit`")); } } while(0); return base; @@ -4616,7 +4616,7 @@ template <> size_t GenericFill(const DB& db, const LIST& params, size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcPropertySet"); } do { // convert the 'HasProperties' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->HasProperties, arg, db ); break; } + try { GenericConvert( in->HasProperties, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcPropertySet to be a `SET [1:?] OF IfcProperty`")); } } while(0); return base; @@ -4628,13 +4628,13 @@ template <> size_t GenericFill(const DB& db, const LIST& if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcPropertySingleValue"); } do { // convert the 'NominalValue' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->NominalValue, arg, db ); break; } + try { GenericConvert( in->NominalValue, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcPropertySingleValue to be a `IfcValue`")); } } while(0); do { // convert the 'Unit' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Unit, arg, db ); break; } + try { GenericConvert( in->Unit, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcPropertySingleValue to be a `IfcUnit`")); } } while(0); return base; @@ -4758,13 +4758,13 @@ template <> size_t GenericFill(const DB& db, const LIST& if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcRectangleProfileDef"); } do { // convert the 'XDim' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->XDim, arg, db ); break; } + try { GenericConvert( in->XDim, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRectangleProfileDef to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'YDim' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->YDim, arg, db ); break; } + try { GenericConvert( in->YDim, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRectangleProfileDef to be a `IfcPositiveLengthMeasure`")); } } while(0); return base; @@ -4850,12 +4850,12 @@ template <> size_t GenericFill(const DB& db, const LIST& param size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelAggregates"); } do { // convert the 'RelatingObject' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->RelatingObject, arg, db ); break; } + try { GenericConvert( in->RelatingObject, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelAggregates to be a `IfcObjectDefinition`")); } } while(0); do { // convert the 'RelatedObjects' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->RelatedObjects, arg, db ); break; } + try { GenericConvert( in->RelatedObjects, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelAggregates to be a `SET [1:?] OF IfcObjectDefinition`")); } } while(0); return base; @@ -4872,12 +4872,12 @@ template <> size_t GenericFill(const DB& db, size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelContainedInSpatialStructure"); } do { // convert the 'RelatedElements' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->RelatedElements, arg, db ); break; } + try { GenericConvert( in->RelatedElements, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelContainedInSpatialStructure to be a `SET [1:?] OF IfcProduct`")); } } while(0); do { // convert the 'RelatingStructure' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->RelatingStructure, arg, db ); break; } + try { GenericConvert( in->RelatingStructure, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelContainedInSpatialStructure to be a `IfcSpatialElement`")); } } while(0); return base; @@ -4894,12 +4894,12 @@ template <> size_t GenericFill(const DB& db, const LI size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelDefinesByProperties"); } do { // convert the 'RelatedObjects' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->RelatedObjects, arg, db ); break; } + try { GenericConvert( in->RelatedObjects, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelDefinesByProperties to be a `SET [1:?] OF IfcObjectDefinition`")); } } while(0); do { // convert the 'RelatingPropertyDefinition' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->RelatingPropertyDefinition, arg, db ); break; } + try { GenericConvert( in->RelatingPropertyDefinition, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelDefinesByProperties to be a `IfcPropertySetDefinitionSelect`")); } } while(0); return base; @@ -4910,12 +4910,12 @@ template <> size_t GenericFill(const DB& db, const LIST& par size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelFillsElement"); } do { // convert the 'RelatingOpeningElement' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->RelatingOpeningElement, arg, db ); break; } + try { GenericConvert( in->RelatingOpeningElement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelFillsElement to be a `IfcOpeningElement`")); } } while(0); do { // convert the 'RelatedBuildingElement' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->RelatedBuildingElement, arg, db ); break; } + try { GenericConvert( in->RelatedBuildingElement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelFillsElement to be a `IfcElement`")); } } while(0); return base; @@ -4926,12 +4926,12 @@ template <> size_t GenericFill(const DB& db, const LIST& par size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelVoidsElement"); } do { // convert the 'RelatingBuildingElement' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->RelatingBuildingElement, arg, db ); break; } + try { GenericConvert( in->RelatingBuildingElement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelVoidsElement to be a `IfcElement`")); } } while(0); do { // convert the 'RelatedOpeningElement' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->RelatedOpeningElement, arg, db ); break; } + try { GenericConvert( in->RelatedOpeningElement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelVoidsElement to be a `IfcFeatureElementSubtraction`")); } } while(0); return base; @@ -4950,27 +4950,27 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcRepresentation"); } do { // convert the 'ContextOfItems' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->ContextOfItems, arg, db ); break; } + try { GenericConvert( in->ContextOfItems, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRepresentation to be a `IfcRepresentationContext`")); } } while(0); do { // convert the 'RepresentationIdentifier' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->RepresentationIdentifier, arg, db ); break; } + try { GenericConvert( in->RepresentationIdentifier, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRepresentation to be a `IfcLabel`")); } } while(0); do { // convert the 'RepresentationType' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->RepresentationType, arg, db ); break; } + try { GenericConvert( in->RepresentationType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcRepresentation to be a `IfcLabel`")); } } while(0); do { // convert the 'Items' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } - try { GenericConvert( in->Items, arg, db ); break; } + try { GenericConvert( in->Items, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRepresentation to be a `SET [1:?] OF IfcRepresentationItem`")); } } while(0); return base; @@ -4981,12 +4981,12 @@ template <> size_t GenericFill(const DB& db, const LIST& p size_t base = 0; if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcRepresentationMap"); } do { // convert the 'MappingOrigin' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->MappingOrigin, arg, db ); break; } + try { GenericConvert( in->MappingOrigin, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcRepresentationMap to be a `IfcAxis2Placement`")); } } while(0); do { // convert the 'MappedRepresentation' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->MappedRepresentation, arg, db ); break; } + try { GenericConvert( in->MappedRepresentation, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcRepresentationMap to be a `IfcRepresentation`")); } } while(0); return base; @@ -4998,13 +4998,13 @@ template <> size_t GenericFill(const DB& db, const LIST& p if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcRevolvedAreaSolid"); } do { // convert the 'Axis' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Axis, arg, db ); break; } + try { GenericConvert( in->Axis, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcRevolvedAreaSolid to be a `IfcAxis1Placement`")); } } while(0); do { // convert the 'Angle' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->Angle, arg, db ); break; } + try { GenericConvert( in->Angle, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcRevolvedAreaSolid to be a `IfcPlaneAngleMeasure`")); } } while(0); return base; @@ -5058,12 +5058,12 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcS if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcSIUnit"); } do { // convert the 'Prefix' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Prefix, arg, db ); break; } + try { GenericConvert( in->Prefix, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSIUnit to be a `IfcSIPrefix`")); } } while(0); do { // convert the 'Name' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Name, arg, db ); break; } + try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcSIUnit to be a `IfcSIUnitName`")); } } while(0); return base; @@ -5144,7 +5144,7 @@ template <> size_t GenericFill(const DB& db, const LI size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcShellBasedSurfaceModel"); } do { // convert the 'SbsmBoundary' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->SbsmBoundary, arg, db ); break; } + try { GenericConvert( in->SbsmBoundary, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcShellBasedSurfaceModel to be a `SET [1:?] OF IfcShell`")); } } while(0); return base; @@ -5156,31 +5156,31 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcSit if (params.GetSize() < 14) { throw STEP::TypeError("expected 14 arguments to IfcSite"); } do { // convert the 'RefLatitude' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->RefLatitude, arg, db ); break; } + try { GenericConvert( in->RefLatitude, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcSite to be a `IfcCompoundPlaneAngleMeasure`")); } } while(0); do { // convert the 'RefLongitude' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->RefLongitude, arg, db ); break; } + try { GenericConvert( in->RefLongitude, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 10 to IfcSite to be a `IfcCompoundPlaneAngleMeasure`")); } } while(0); do { // convert the 'RefElevation' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->RefElevation, arg, db ); break; } + try { GenericConvert( in->RefElevation, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 11 to IfcSite to be a `IfcLengthMeasure`")); } } while(0); do { // convert the 'LandTitleNumber' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->LandTitleNumber, arg, db ); break; } + try { GenericConvert( in->LandTitleNumber, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 12 to IfcSite to be a `IfcLabel`")); } } while(0); do { // convert the 'SiteAddress' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->SiteAddress, arg, db ); break; } + try { GenericConvert( in->SiteAddress, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 13 to IfcSite to be a `IfcPostalAddress`")); } } while(0); return base; @@ -5234,13 +5234,13 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcSp if (params.GetSize() < 11) { throw STEP::TypeError("expected 11 arguments to IfcSpace"); } do { // convert the 'PredefinedType' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->PredefinedType, arg, db ); break; } + try { GenericConvert( in->PredefinedType, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 9 to IfcSpace to be a `IfcSpaceTypeEnum`")); } } while(0); do { // convert the 'ElevationWithFlooring' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ElevationWithFlooring, arg, db ); break; } + try { GenericConvert( in->ElevationWithFlooring, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 10 to IfcSpace to be a `IfcLengthMeasure`")); } } while(0); return base; @@ -5539,18 +5539,18 @@ template <> size_t GenericFill(const DB& db, const LIST& params, if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcStyledItem"); } do { // convert the 'Item' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Item, arg, db ); break; } + try { GenericConvert( in->Item, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcStyledItem to be a `IfcRepresentationItem`")); } } while(0); do { // convert the 'Styles' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Styles, arg, db ); break; } + try { GenericConvert( in->Styles, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcStyledItem to be a `SET [1:?] OF IfcStyleAssignmentSelect`")); } } while(0); do { // convert the 'Name' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Name, arg, db ); break; } + try { GenericConvert( in->Name, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcStyledItem to be a `IfcLabel`")); } } while(0); return base; @@ -5624,12 +5624,12 @@ template <> size_t GenericFill(const DB& db, const LIST& params size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 3) { throw STEP::TypeError("expected 3 arguments to IfcSurfaceStyle"); } do { // convert the 'Side' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Side, arg, db ); break; } + try { GenericConvert( in->Side, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSurfaceStyle to be a `IfcSurfaceSide`")); } } while(0); do { // convert the 'Styles' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Styles, arg, db ); break; } + try { GenericConvert( in->Styles, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSurfaceStyle to be a `SET [1:5] OF IfcSurfaceStyleElementSelect`")); } } while(0); return base; @@ -5641,14 +5641,14 @@ template <> size_t GenericFill(const DB& db, const LIST& if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcSurfaceStyleShading"); } do { // convert the 'SurfaceColour' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->SurfaceColour, arg, db ); break; } + try { GenericConvert( in->SurfaceColour, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSurfaceStyleShading to be a `IfcColourRgb`")); } } while(0); do { // convert the 'Transparency' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->Transparency, arg, db ); break; } + try { GenericConvert( in->Transparency, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSurfaceStyleShading to be a `IfcNormalisedRatioMeasure`")); } } while(0); return base; @@ -5660,42 +5660,42 @@ template <> size_t GenericFill(const DB& db, const LIS if (params.GetSize() < 9) { throw STEP::TypeError("expected 9 arguments to IfcSurfaceStyleRendering"); } do { // convert the 'DiffuseColour' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->DiffuseColour, arg, db ); break; } + try { GenericConvert( in->DiffuseColour, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } } while(0); do { // convert the 'TransmissionColour' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->TransmissionColour, arg, db ); break; } + try { GenericConvert( in->TransmissionColour, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } } while(0); do { // convert the 'DiffuseTransmissionColour' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->DiffuseTransmissionColour, arg, db ); break; } + try { GenericConvert( in->DiffuseTransmissionColour, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } } while(0); do { // convert the 'ReflectionColour' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->ReflectionColour, arg, db ); break; } + try { GenericConvert( in->ReflectionColour, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } } while(0); do { // convert the 'SpecularColour' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->SpecularColour, arg, db ); break; } + try { GenericConvert( in->SpecularColour, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcSurfaceStyleRendering to be a `IfcColourOrFactor`")); } } while(0); do { // convert the 'SpecularHighlight' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->SpecularHighlight, arg, db ); break; } + try { GenericConvert( in->SpecularHighlight, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 7 to IfcSurfaceStyleRendering to be a `IfcSpecularHighlightSelect`")); } } while(0); do { // convert the 'ReflectanceMethod' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->ReflectanceMethod, arg, db ); break; } + try { GenericConvert( in->ReflectanceMethod, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 8 to IfcSurfaceStyleRendering to be a `IfcReflectanceMethodEnum`")); } } while(0); return base; @@ -5706,7 +5706,7 @@ template <> size_t GenericFill(const DB& db, const size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcSurfaceStyleWithTextures"); } do { // convert the 'Textures' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Textures, arg, db ); break; } + try { GenericConvert( in->Textures, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSurfaceStyleWithTextures to be a `LIST [1:?] OF IfcSurfaceTexture`")); } } while(0); return base; @@ -5718,34 +5718,34 @@ template <> size_t GenericFill(const DB& db, const LIST& para if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcSweptDiskSolid"); } do { // convert the 'Directrix' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[0]=true; break; } - try { GenericConvert( in->Directrix, arg, db ); break; } + try { GenericConvert( in->Directrix, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcSweptDiskSolid to be a `IfcCurve`")); } } while(0); do { // convert the 'Radius' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[1]=true; break; } - try { GenericConvert( in->Radius, arg, db ); break; } + try { GenericConvert( in->Radius, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcSweptDiskSolid to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'InnerRadius' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[2]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->InnerRadius, arg, db ); break; } + try { GenericConvert( in->InnerRadius, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcSweptDiskSolid to be a `IfcPositiveLengthMeasure`")); } } while(0); do { // convert the 'StartParam' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[3]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->StartParam, arg, db ); break; } + try { GenericConvert( in->StartParam, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcSweptDiskSolid to be a `IfcParameterValue`")); } } while(0); do { // convert the 'EndParam' argument boost::shared_ptr arg = params[base++]; if (dynamic_cast(&*arg)) { in->ObjectHelper::aux_is_derived[4]=true; break; } if (dynamic_cast(&*arg)) break; - try { GenericConvert( in->EndParam, arg, db ); break; } + try { GenericConvert( in->EndParam, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcSweptDiskSolid to be a `IfcParameterValue`")); } } while(0); return base; @@ -5924,27 +5924,27 @@ template <> size_t GenericFill(const DB& db, const LIST& params size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 5) { throw STEP::TypeError("expected 5 arguments to IfcTrimmedCurve"); } do { // convert the 'BasisCurve' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->BasisCurve, arg, db ); break; } + try { GenericConvert( in->BasisCurve, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcTrimmedCurve to be a `IfcCurve`")); } } while(0); do { // convert the 'Trim1' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Trim1, arg, db ); break; } + try { GenericConvert( in->Trim1, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcTrimmedCurve to be a `SET [1:2] OF IfcTrimmingSelect`")); } } while(0); do { // convert the 'Trim2' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Trim2, arg, db ); break; } + try { GenericConvert( in->Trim2, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcTrimmedCurve to be a `SET [1:2] OF IfcTrimmingSelect`")); } } while(0); do { // convert the 'SenseAgreement' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->SenseAgreement, arg, db ); break; } + try { GenericConvert( in->SenseAgreement, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcTrimmedCurve to be a `IfcBoolean`")); } } while(0); do { // convert the 'MasterRepresentation' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->MasterRepresentation, arg, db ); break; } + try { GenericConvert( in->MasterRepresentation, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcTrimmedCurve to be a `IfcTrimmingPreference`")); } } while(0); return base; @@ -5976,7 +5976,7 @@ template <> size_t GenericFill(const DB& db, const LIST& para size_t base = 0; if (params.GetSize() < 1) { throw STEP::TypeError("expected 1 arguments to IfcUnitAssignment"); } do { // convert the 'Units' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Units, arg, db ); break; } + try { GenericConvert( in->Units, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcUnitAssignment to be a `SET [1:?] OF IfcUnit`")); } } while(0); return base; @@ -6029,12 +6029,12 @@ template <> size_t GenericFill(const DB& db, const LIST& params, IfcV size_t base = GenericFill(db,params,static_cast(in)); if (params.GetSize() < 2) { throw STEP::TypeError("expected 2 arguments to IfcVector"); } do { // convert the 'Orientation' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Orientation, arg, db ); break; } + try { GenericConvert( in->Orientation, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 0 to IfcVector to be a `IfcDirection`")); } } while(0); do { // convert the 'Magnitude' argument boost::shared_ptr arg = params[base++]; - try { GenericConvert( in->Magnitude, arg, db ); break; } + try { GenericConvert( in->Magnitude, arg, db ); break; } catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 1 to IfcVector to be a `IfcLengthMeasure`")); } } while(0); return base; diff --git a/code/AssetLib/IFC/IFCReaderGen_4.h b/code/AssetLib/IFC/IFCReaderGen_4.h index 0f184cd02..abf021911 100644 --- a/code/AssetLib/IFC/IFCReaderGen_4.h +++ b/code/AssetLib/IFC/IFCReaderGen_4.h @@ -5,8 +5,8 @@ Open Asset Import Library (ASSIMP) Copyright (c) 2006-2020, ASSIMP Development Team All rights reserved. -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the +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 @@ -23,16 +23,16 @@ following conditions are met: derived from this software without specific prior written permission of the ASSIMP Development Team. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +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 +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 +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 +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. ---------------------------------------------------------------------- @@ -51,12 +51,12 @@ namespace Schema_4 { using namespace STEP; using namespace STEP::EXPRESS; - - + + struct NotImplemented : public ObjectHelper { - + }; - + // ****************************************************************************** // IFC Custom data types diff --git a/code/AssetLib/IFC/IFCUtil.h b/code/AssetLib/IFC/IFCUtil.h index a1190746b..b18f35052 100644 --- a/code/AssetLib/IFC/IFCUtil.h +++ b/code/AssetLib/IFC/IFCUtil.h @@ -54,6 +54,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include + struct aiNode; namespace Assimp { @@ -137,14 +139,10 @@ struct TempOpening } // ------------------------------------------------------------------------------ - TempOpening(const IFC::Schema_2x3::IfcSolidModel* solid,IfcVector3 extrusionDir, - std::shared_ptr profileMesh, - std::shared_ptr profileMesh2D) - : solid(solid) - , extrusionDir(extrusionDir) - , profileMesh(profileMesh) - , profileMesh2D(profileMesh2D) - { + TempOpening(const IFC::Schema_2x3::IfcSolidModel *solid, IfcVector3 extrusionDir, + std::shared_ptr profileMesh, + std::shared_ptr profileMesh2D) : + solid(solid), extrusionDir(extrusionDir), profileMesh(std::move(profileMesh)), profileMesh2D(std::move(profileMesh2D)) { } // ------------------------------------------------------------------------------ diff --git a/code/AssetLib/Irr/IRRLoader.h b/code/AssetLib/Irr/IRRLoader.h index 535f6481d..da90902ed 100644 --- a/code/AssetLib/Irr/IRRLoader.h +++ b/code/AssetLib/Irr/IRRLoader.h @@ -273,7 +273,7 @@ private: std::vector& anims); private: - /// Configuration option: desired output FPS + /// Configuration option: desired output FPS double fps; /// Configuration option: speed flag was set? diff --git a/code/AssetLib/LWO/LWOAnimation.h b/code/AssetLib/LWO/LWOAnimation.h index 1ed8caf88..64aa5980a 100644 --- a/code/AssetLib/LWO/LWOAnimation.h +++ b/code/AssetLib/LWO/LWOAnimation.h @@ -114,7 +114,7 @@ enum PrePostBehaviour /** \brief Data structure for a LWO animation keyframe */ struct Key { - Key() AI_NO_EXCEPT + Key() AI_NO_EXCEPT : time() , value() , inter(IT_LINE) diff --git a/code/AssetLib/LWS/LWSLoader.cpp b/code/AssetLib/LWS/LWSLoader.cpp index d469a1064..cb07787fa 100644 --- a/code/AssetLib/LWS/LWSLoader.cpp +++ b/code/AssetLib/LWS/LWSLoader.cpp @@ -200,7 +200,7 @@ void LWSImporter::ReadEnvelope(const LWS::Element &dad, LWO::Envelope &fill) { // reserve enough storage std::list::const_iterator it = dad.children.begin(); - + fill.keys.reserve(strtoul10(it->tokens[1].c_str())); for (++it; it != dad.children.end(); ++it) { @@ -318,7 +318,7 @@ void LWSImporter::SetupNodeName(aiNode *nd, LWS::NodeDesc &src) { } else { ++s; } - std::string::size_type t = src.path.substr(s).find_last_of("."); + std::string::size_type t = src.path.substr(s).find_last_of('.'); nd->mName.length = ::ai_snprintf(nd->mName.data, MAXLEN, "%s_(%08X)", src.path.substr(s).substr(0, t).c_str(), combined); return; @@ -466,7 +466,7 @@ std::string LWSImporter::FindLWOFile(const std::string &in) { std::string tmp(in); if (in.length() > 3 && in[1] == ':' && in[2] != '\\' && in[2] != '/') { tmp = in[0] + (std::string(":\\") + in.substr(2)); - } + } if (io->Exists(tmp)) { return in; diff --git a/code/AssetLib/M3D/M3DImporter.cpp b/code/AssetLib/M3D/M3DImporter.cpp index 38bbd1d4a..efa1d5475 100644 --- a/code/AssetLib/M3D/M3DImporter.cpp +++ b/code/AssetLib/M3D/M3DImporter.cpp @@ -233,12 +233,12 @@ void M3DImporter::importMaterials(const M3DWrapper &m3d) { ASSIMP_LOG_DEBUG("M3D: importMaterials ", mScene->mNumMaterials); // add a default material as first - aiMaterial *mat = new aiMaterial; - mat->AddProperty(&name, AI_MATKEY_NAME); + aiMaterial *defaultMat = new aiMaterial; + defaultMat->AddProperty(&name, AI_MATKEY_NAME); c.a = 1.0f; c.b = c.g = c.r = 0.6f; - mat->AddProperty(&c, 1, AI_MATKEY_COLOR_DIFFUSE); - mScene->mMaterials[0] = mat; + defaultMat->AddProperty(&c, 1, AI_MATKEY_COLOR_DIFFUSE); + mScene->mMaterials[0] = defaultMat; if (!m3d->nummaterial || !m3d->material) { return; @@ -300,12 +300,12 @@ void M3DImporter::importMaterials(const M3DWrapper &m3d) { m->prop[j].value.textureid < m3d->numtexture && m3d->texture[m->prop[j].value.textureid].name) { name.Set(std::string(std::string(m3d->texture[m->prop[j].value.textureid].name) + ".png")); - mat->AddProperty(&name, aiTxProps[k].pKey, aiTxProps[k].type, aiTxProps[k].index); + newMat->AddProperty(&name, aiTxProps[k].pKey, aiTxProps[k].type, aiTxProps[k].index); n = 0; - mat->AddProperty(&n, 1, _AI_MATKEY_UVWSRC_BASE, aiProps[k].type, aiProps[k].index); + newMat->AddProperty(&n, 1, _AI_MATKEY_UVWSRC_BASE, aiProps[k].type, aiProps[k].index); } } - mScene->mMaterials[i + 1] = mat; + mScene->mMaterials[i + 1] = newMat; } } @@ -655,7 +655,7 @@ void M3DImporter::convertPose(const M3DWrapper &m3d, aiMatrix4x4 *m, unsigned in // ------------------------------------------------------------------------------------------------ // find a node by name -aiNode *M3DImporter::findNode(aiNode *pNode, aiString name) { +aiNode *M3DImporter::findNode(aiNode *pNode, const aiString &name) { ai_assert(pNode != nullptr); ai_assert(mScene != nullptr); diff --git a/code/AssetLib/M3D/M3DImporter.h b/code/AssetLib/M3D/M3DImporter.h index 7a2a9fbd3..05e8ced7b 100644 --- a/code/AssetLib/M3D/M3DImporter.h +++ b/code/AssetLib/M3D/M3DImporter.h @@ -89,8 +89,8 @@ private: // helper functions aiColor4D mkColor(uint32_t c); void convertPose(const M3DWrapper &m3d, aiMatrix4x4 *m, unsigned int posid, unsigned int orientid); - aiNode *findNode(aiNode *pNode, aiString name); - void calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m); + aiNode *findNode(aiNode *pNode, const aiString &name); + void calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m); void populateMesh(const M3DWrapper &m3d, aiMesh *pMesh, std::vector *faces, std::vector *verteces, std::vector *normals, std::vector *texcoords, std::vector *colors, std::vector *vertexids); diff --git a/code/AssetLib/M3D/M3DWrapper.h b/code/AssetLib/M3D/M3DWrapper.h index 782e908d2..dcb82a83a 100644 --- a/code/AssetLib/M3D/M3DWrapper.h +++ b/code/AssetLib/M3D/M3DWrapper.h @@ -46,6 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef AI_M3DWRAPPER_H_INC #define AI_M3DWRAPPER_H_INC + #if !(ASSIMP_BUILD_NO_EXPORT || ASSIMP_BUILD_NO_M3D_EXPORTER) || !ASSIMP_BUILD_NO_M3D_IMPORTER #include @@ -55,44 +56,75 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Assimp specific M3D configuration. Comment out these defines to remove functionality //#define ASSIMP_USE_M3D_READFILECB +// Share stb_image's PNG loader with other importers/exporters instead of bringing our own copy. +#define STBI_ONLY_PNG +#include + #include "m3d.h" namespace Assimp { + class IOSystem; +/// brief The M3D-Wrapper, provudes c++ access to the data. class M3DWrapper { - m3d_t *m3d_ = nullptr; - unsigned char *saved_output_ = nullptr; - public: - // Construct an empty M3D model + /// Construct an empty M3D model explicit M3DWrapper(); - // Construct an M3D model from provided buffer - // NOTE: The m3d.h SDK function does not mark the data as const. Have assumed it does not write. - // BUG: SECURITY: The m3d.h SDK cannot be informed of the buffer size. BUFFER OVERFLOW IS CERTAIN + /// Construct an M3D model from provided buffer + /// @note The m3d.h SDK function does not mark the data as const. Have assumed it does not write. + /// BUG: SECURITY: The m3d.h SDK cannot be informed of the buffer size. BUFFER OVERFLOW IS CERTAIN explicit M3DWrapper(IOSystem *pIOHandler, const std::vector &buffer); - ~M3DWrapper(); + /// Theclasss destructor. + ~M3DWrapper(); - void reset(); + /// Will reset the wrapper, all data will become nullptr. + void reset(); - // Name - inline std::string Name() const { - if (m3d_) return std::string(m3d_->name); - return std::string(); - } + // The Name access, empty string returned when no m3d instance. + std::string Name() const; - // Execute a save + /// Executes a save. unsigned char *Save(int quality, int flags, unsigned int &size); + + /// Clearer void ClearSave(); - inline explicit operator bool() const { return m3d_ != nullptr; } + /// True for m3d instance exists. + explicit operator bool() const; // Allow direct access to M3D API - inline m3d_t *operator->() const { return m3d_; } - inline m3d_t *M3D() const { return m3d_; } + m3d_t *operator->() const; + m3d_t *M3D() const; + +private: + m3d_t *m3d_ = nullptr; + unsigned char *saved_output_ = nullptr; }; + +inline std::string M3DWrapper::Name() const { + if (nullptr != m3d_) { + if (nullptr != m3d_->name) { + return std::string(m3d_->name); + } + } + return std::string(); +} + +inline M3DWrapper::operator bool() const { + return m3d_ != nullptr; +} + +inline m3d_t *M3DWrapper::operator->() const { + return m3d_; +} + +inline m3d_t *M3DWrapper::M3D() const { + return m3d_; +} + } // namespace Assimp #endif diff --git a/code/AssetLib/M3D/m3d.h b/code/AssetLib/M3D/m3d.h index 68265959e..3adcd5bef 100644 --- a/code/AssetLib/M3D/m3d.h +++ b/code/AssetLib/M3D/m3d.h @@ -622,1230 +622,6 @@ static m3dcd_t m3d_commandtypes[] = { #include #include -#if !defined(M3D_NOIMPORTER) && !defined(STBI_INCLUDE_STB_IMAGE_H) -/* PNG decompressor from - - stb_image - v2.23 - public domain image loader - http://nothings.org/stb_image.h -*/ -static const char *_m3dstbi__g_failure_reason; - -enum { - STBI_default = 0, - - STBI_grey = 1, - STBI_grey_alpha = 2, - STBI_rgb = 3, - STBI_rgb_alpha = 4 -}; - -enum { - STBI__SCAN_load = 0, - STBI__SCAN_type, - STBI__SCAN_header -}; - -typedef unsigned short _m3dstbi_us; - -typedef uint16_t _m3dstbi__uint16; -typedef int16_t _m3dstbi__int16; -typedef uint32_t _m3dstbi__uint32; -typedef int32_t _m3dstbi__int32; - -typedef struct -{ - _m3dstbi__uint32 img_x, img_y; - int img_n, img_out_n; - - void *io_user_data; - - int read_from_callbacks; - int buflen; - unsigned char buffer_start[128]; - - unsigned char *img_buffer, *img_buffer_end; - unsigned char *img_buffer_original, *img_buffer_original_end; -} _m3dstbi__context; - -typedef struct -{ - int bits_per_channel; - int num_channels; - int channel_order; -} _m3dstbi__result_info; - -#define STBI_ASSERT(v) -#ifdef _MSC_VER -#define STBI_NOTUSED(v) (void)(v) -#else -#define STBI_NOTUSED(v) (void)sizeof(v) -#endif -#define STBI__BYTECAST(x) ((unsigned char)((x)&255)) -#define STBI_MALLOC(sz) M3D_MALLOC(sz) -#define STBI_REALLOC(p, newsz) M3D_REALLOC(p, newsz) -#define STBI_FREE(p) M3D_FREE(p) -#define STBI_REALLOC_SIZED(p, oldsz, newsz) STBI_REALLOC(p, newsz) - -_inline static unsigned char _m3dstbi__get8(_m3dstbi__context *s) { - if (s->img_buffer < s->img_buffer_end) - return *s->img_buffer++; - return 0; -} - -static void _m3dstbi__skip(_m3dstbi__context *s, int n) { - if (n < 0) { - s->img_buffer = s->img_buffer_end; - return; - } - s->img_buffer += n; -} - -static int _m3dstbi__getn(_m3dstbi__context *s, unsigned char *buffer, int n) { - if (s->img_buffer + n <= s->img_buffer_end) { - memcpy(buffer, s->img_buffer, n); - s->img_buffer += n; - return 1; - } else - return 0; -} - -static int _m3dstbi__get16be(_m3dstbi__context *s) { - int z = _m3dstbi__get8(s); - return (z << 8) + _m3dstbi__get8(s); -} - -static _m3dstbi__uint32 _m3dstbi__get32be(_m3dstbi__context *s) { - _m3dstbi__uint32 z = _m3dstbi__get16be(s); - return (z << 16) + _m3dstbi__get16be(s); -} - -#define _m3dstbi__err(x, y) _m3dstbi__errstr(y) -static int _m3dstbi__errstr(const char *str) { - _m3dstbi__g_failure_reason = str; - return 0; -} - -_inline static void *_m3dstbi__malloc(size_t size) { - return STBI_MALLOC(size); -} - -static int _m3dstbi__addsizes_valid(int a, int b) { - if (b < 0) return 0; - return a <= 2147483647 - b; -} - -static int _m3dstbi__mul2sizes_valid(int a, int b) { - if (a < 0 || b < 0) return 0; - if (b == 0) return 1; - return a <= 2147483647 / b; -} - -static int _m3dstbi__mad2sizes_valid(int a, int b, int add) { - return _m3dstbi__mul2sizes_valid(a, b) && _m3dstbi__addsizes_valid(a * b, add); -} - -static int _m3dstbi__mad3sizes_valid(int a, int b, int c, int add) { - return _m3dstbi__mul2sizes_valid(a, b) && _m3dstbi__mul2sizes_valid(a * b, c) && - _m3dstbi__addsizes_valid(a * b * c, add); -} - -static void *_m3dstbi__malloc_mad2(int a, int b, int add) { - if (!_m3dstbi__mad2sizes_valid(a, b, add)) return NULL; - return _m3dstbi__malloc(a * b + add); -} - -static void *_m3dstbi__malloc_mad3(int a, int b, int c, int add) { - if (!_m3dstbi__mad3sizes_valid(a, b, c, add)) return NULL; - return _m3dstbi__malloc(a * b * c + add); -} - -static unsigned char _m3dstbi__compute_y(int r, int g, int b) { - return (unsigned char)(((r * 77) + (g * 150) + (29 * b)) >> 8); -} - -static unsigned char *_m3dstbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) { - int i, j; - unsigned char *good; - - if (req_comp == img_n) return data; - STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - - good = (unsigned char *)_m3dstbi__malloc_mad3(req_comp, x, y, 0); - if (good == NULL) { - STBI_FREE(data); - _m3dstbi__err("outofmem", "Out of memory"); - return NULL; - } - - for (j = 0; j < (int)y; ++j) { - unsigned char *src = data + j * x * img_n; - unsigned char *dest = good + j * x * req_comp; - -#define STBI__COMBO(a, b) ((a)*8 + (b)) -#define STBI__CASE(a, b) \ - case STBI__COMBO(a, b): \ - for (i = x - 1; i >= 0; --i, src += a, dest += b) - switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1, 2) { dest[0] = src[0], dest[1] = 255; } - break; - STBI__CASE(1, 3) { dest[0] = dest[1] = dest[2] = src[0]; } - break; - STBI__CASE(1, 4) { dest[0] = dest[1] = dest[2] = src[0], dest[3] = 255; } - break; - STBI__CASE(2, 1) { dest[0] = src[0]; } - break; - STBI__CASE(2, 3) { dest[0] = dest[1] = dest[2] = src[0]; } - break; - STBI__CASE(2, 4) { dest[0] = dest[1] = dest[2] = src[0], dest[3] = src[1]; } - break; - STBI__CASE(3, 4) { dest[0] = src[0], dest[1] = src[1], dest[2] = src[2], dest[3] = 255; } - break; - STBI__CASE(3, 1) { dest[0] = _m3dstbi__compute_y(src[0], src[1], src[2]); } - break; - STBI__CASE(3, 2) { dest[0] = _m3dstbi__compute_y(src[0], src[1], src[2]), dest[1] = 255; } - break; - STBI__CASE(4, 1) { dest[0] = _m3dstbi__compute_y(src[0], src[1], src[2]); } - break; - STBI__CASE(4, 2) { dest[0] = _m3dstbi__compute_y(src[0], src[1], src[2]), dest[1] = src[3]; } - break; - STBI__CASE(4, 3) { dest[0] = src[0], dest[1] = src[1], dest[2] = src[2]; } - break; - default: STBI_ASSERT(0); - } -#undef STBI__CASE - } - - STBI_FREE(data); - return good; -} - -static _m3dstbi__uint16 _m3dstbi__compute_y_16(int r, int g, int b) { - return (_m3dstbi__uint16)(((r * 77) + (g * 150) + (29 * b)) >> 8); -} - -static _m3dstbi__uint16 *_m3dstbi__convert_format16(_m3dstbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) { - int i, j; - _m3dstbi__uint16 *good; - - if (req_comp == img_n) return data; - STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - - good = (_m3dstbi__uint16 *)_m3dstbi__malloc(req_comp * x * y * 2); - if (good == NULL) { - STBI_FREE(data); - _m3dstbi__err("outofmem", "Out of memory"); - return NULL; - } - - for (j = 0; j < (int)y; ++j) { - _m3dstbi__uint16 *src = data + j * x * img_n; - _m3dstbi__uint16 *dest = good + j * x * req_comp; - -#define STBI__COMBO(a, b) ((a)*8 + (b)) -#define STBI__CASE(a, b) \ - case STBI__COMBO(a, b): \ - for (i = x - 1; i >= 0; --i, src += a, dest += b) - switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1, 2) { dest[0] = src[0], dest[1] = 0xffff; } - break; - STBI__CASE(1, 3) { dest[0] = dest[1] = dest[2] = src[0]; } - break; - STBI__CASE(1, 4) { dest[0] = dest[1] = dest[2] = src[0], dest[3] = 0xffff; } - break; - STBI__CASE(2, 1) { dest[0] = src[0]; } - break; - STBI__CASE(2, 3) { dest[0] = dest[1] = dest[2] = src[0]; } - break; - STBI__CASE(2, 4) { dest[0] = dest[1] = dest[2] = src[0], dest[3] = src[1]; } - break; - STBI__CASE(3, 4) { dest[0] = src[0], dest[1] = src[1], dest[2] = src[2], dest[3] = 0xffff; } - break; - STBI__CASE(3, 1) { dest[0] = _m3dstbi__compute_y_16(src[0], src[1], src[2]); } - break; - STBI__CASE(3, 2) { dest[0] = _m3dstbi__compute_y_16(src[0], src[1], src[2]), dest[1] = 0xffff; } - break; - STBI__CASE(4, 1) { dest[0] = _m3dstbi__compute_y_16(src[0], src[1], src[2]); } - break; - STBI__CASE(4, 2) { dest[0] = _m3dstbi__compute_y_16(src[0], src[1], src[2]), dest[1] = src[3]; } - break; - STBI__CASE(4, 3) { dest[0] = src[0], dest[1] = src[1], dest[2] = src[2]; } - break; - default: STBI_ASSERT(0); - } -#undef STBI__CASE - } - - STBI_FREE(data); - return good; -} - -#define STBI__ZFAST_BITS 9 -#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) - -typedef struct -{ - _m3dstbi__uint16 fast[1 << STBI__ZFAST_BITS]; - _m3dstbi__uint16 firstcode[16]; - int maxcode[17]; - _m3dstbi__uint16 firstsymbol[16]; - unsigned char size[288]; - _m3dstbi__uint16 value[288]; -} _m3dstbi__zhuffman; - -_inline static int _m3dstbi__bitreverse16(int n) { - n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); - n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); - n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); - n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); - return n; -} - -_inline static int _m3dstbi__bit_reverse(int v, int bits) { - STBI_ASSERT(bits <= 16); - return _m3dstbi__bitreverse16(v) >> (16 - bits); -} - -static int _m3dstbi__zbuild_huffman(_m3dstbi__zhuffman *z, unsigned char *sizelist, int num) { - int i, k = 0; - int code, next_code[16], sizes[17]; - - memset(sizes, 0, sizeof(sizes)); - memset(z->fast, 0, sizeof(z->fast)); - for (i = 0; i < num; ++i) - ++sizes[sizelist[i]]; - sizes[0] = 0; - for (i = 1; i < 16; ++i) - if (sizes[i] > (1 << i)) - return _m3dstbi__err("bad sizes", "Corrupt PNG"); - code = 0; - for (i = 1; i < 16; ++i) { - next_code[i] = code; - z->firstcode[i] = (_m3dstbi__uint16)code; - z->firstsymbol[i] = (_m3dstbi__uint16)k; - code = (code + sizes[i]); - if (sizes[i]) - if (code - 1 >= (1 << i)) return _m3dstbi__err("bad codelengths", "Corrupt PNG"); - z->maxcode[i] = code << (16 - i); - code <<= 1; - k += sizes[i]; - } - z->maxcode[16] = 0x10000; - for (i = 0; i < num; ++i) { - int s = sizelist[i]; - if (s) { - int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; - _m3dstbi__uint16 fastv = (_m3dstbi__uint16)((s << 9) | i); - z->size[c] = (unsigned char)s; - z->value[c] = (_m3dstbi__uint16)i; - if (s <= STBI__ZFAST_BITS) { - int j = _m3dstbi__bit_reverse(next_code[s], s); - while (j < (1 << STBI__ZFAST_BITS)) { - z->fast[j] = fastv; - j += (1 << s); - } - } - ++next_code[s]; - } - } - return 1; -} - -typedef struct -{ - unsigned char *zbuffer, *zbuffer_end; - int num_bits; - _m3dstbi__uint32 code_buffer; - - char *zout; - char *zout_start; - char *zout_end; - int z_expandable; - - _m3dstbi__zhuffman z_length, z_distance; -} _m3dstbi__zbuf; - -_inline static unsigned char _m3dstbi__zget8(_m3dstbi__zbuf *z) { - if (z->zbuffer >= z->zbuffer_end) return 0; - return *z->zbuffer++; -} - -static void _m3dstbi__fill_bits(_m3dstbi__zbuf *z) { - do { - STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); - z->code_buffer |= (unsigned int)_m3dstbi__zget8(z) << z->num_bits; - z->num_bits += 8; - } while (z->num_bits <= 24); -} - -_inline static unsigned int _m3dstbi__zreceive(_m3dstbi__zbuf *z, int n) { - unsigned int k; - if (z->num_bits < n) _m3dstbi__fill_bits(z); - k = z->code_buffer & ((1 << n) - 1); - z->code_buffer >>= n; - z->num_bits -= n; - return k; -} - -static int _m3dstbi__zhuffman_decode_slowpath(_m3dstbi__zbuf *a, _m3dstbi__zhuffman *z) { - int b, s, k; - k = _m3dstbi__bit_reverse(a->code_buffer, 16); - for (s = STBI__ZFAST_BITS + 1;; ++s) - if (k < z->maxcode[s]) - break; - if (s == 16) return -1; - b = (k >> (16 - s)) - z->firstcode[s] + z->firstsymbol[s]; - STBI_ASSERT(z->size[b] == s); - a->code_buffer >>= s; - a->num_bits -= s; - return z->value[b]; -} - -_inline static int _m3dstbi__zhuffman_decode(_m3dstbi__zbuf *a, _m3dstbi__zhuffman *z) { - int b, s; - if (a->num_bits < 16) _m3dstbi__fill_bits(a); - b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; - if (b) { - s = b >> 9; - a->code_buffer >>= s; - a->num_bits -= s; - return b & 511; - } - return _m3dstbi__zhuffman_decode_slowpath(a, z); -} - -static int _m3dstbi__zexpand(_m3dstbi__zbuf *z, char *zout, int n) { - char *q; - int cur, limit, old_limit; - z->zout = zout; - if (!z->z_expandable) return _m3dstbi__err("output buffer limit", "Corrupt PNG"); - cur = (int)(z->zout - z->zout_start); - limit = old_limit = (int)(z->zout_end - z->zout_start); - while (cur + n > limit) - limit *= 2; - q = (char *)STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); - STBI_NOTUSED(old_limit); - if (q == NULL) return _m3dstbi__err("outofmem", "Out of memory"); - z->zout_start = q; - z->zout = q + cur; - z->zout_end = q + limit; - return 1; -} - -static int _m3dstbi__zlength_base[31] = { - 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, - 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, - 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 -}; - -static int _m3dstbi__zlength_extra[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 }; - -static int _m3dstbi__zdist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, - 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 }; - -static int _m3dstbi__zdist_extra[32] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; - -static int _m3dstbi__parse_huffman_block(_m3dstbi__zbuf *a) { - char *zout = a->zout; - for (;;) { - int z = _m3dstbi__zhuffman_decode(a, &a->z_length); - if (z < 256) { - if (z < 0) return _m3dstbi__err("bad huffman code", "Corrupt PNG"); - if (zout >= a->zout_end) { - if (!_m3dstbi__zexpand(a, zout, 1)) return 0; - zout = a->zout; - } - *zout++ = (char)z; - } else { - unsigned char *p; - int len, dist; - if (z == 256) { - a->zout = zout; - return 1; - } - z -= 257; - len = _m3dstbi__zlength_base[z]; - if (_m3dstbi__zlength_extra[z]) len += _m3dstbi__zreceive(a, _m3dstbi__zlength_extra[z]); - z = _m3dstbi__zhuffman_decode(a, &a->z_distance); - if (z < 0) return _m3dstbi__err("bad huffman code", "Corrupt PNG"); - dist = _m3dstbi__zdist_base[z]; - if (_m3dstbi__zdist_extra[z]) dist += _m3dstbi__zreceive(a, _m3dstbi__zdist_extra[z]); - if (zout - a->zout_start < dist) return _m3dstbi__err("bad dist", "Corrupt PNG"); - if (zout + len > a->zout_end) { - if (!_m3dstbi__zexpand(a, zout, len)) return 0; - zout = a->zout; - } - p = (unsigned char *)(zout - dist); - if (dist == 1) { - unsigned char v = *p; - if (len) { - do - *zout++ = v; - while (--len); - } - } else { - if (len) { - do - *zout++ = *p++; - while (--len); - } - } - } - } -} - -static int _m3dstbi__compute_huffman_codes(_m3dstbi__zbuf *a) { - static unsigned char length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; - _m3dstbi__zhuffman z_codelength; - unsigned char lencodes[286 + 32 + 137]; - unsigned char codelength_sizes[19]; - int i, n; - - int hlit = _m3dstbi__zreceive(a, 5) + 257; - int hdist = _m3dstbi__zreceive(a, 5) + 1; - int hclen = _m3dstbi__zreceive(a, 4) + 4; - int ntot = hlit + hdist; - - memset(codelength_sizes, 0, sizeof(codelength_sizes)); - for (i = 0; i < hclen; ++i) { - int s = _m3dstbi__zreceive(a, 3); - codelength_sizes[length_dezigzag[i]] = (unsigned char)s; - } - if (!_m3dstbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; - - n = 0; - while (n < ntot) { - int c = _m3dstbi__zhuffman_decode(a, &z_codelength); - if (c < 0 || c >= 19) return _m3dstbi__err("bad codelengths", "Corrupt PNG"); - if (c < 16) - lencodes[n++] = (unsigned char)c; - else { - unsigned char fill = 0; - if (c == 16) { - c = _m3dstbi__zreceive(a, 2) + 3; - if (n == 0) return _m3dstbi__err("bad codelengths", "Corrupt PNG"); - fill = lencodes[n - 1]; - } else if (c == 17) - c = _m3dstbi__zreceive(a, 3) + 3; - else { - STBI_ASSERT(c == 18); - c = _m3dstbi__zreceive(a, 7) + 11; - } - if (ntot - n < c) return _m3dstbi__err("bad codelengths", "Corrupt PNG"); - memset(lencodes + n, fill, c); - n += c; - } - } - if (n != ntot) return _m3dstbi__err("bad codelengths", "Corrupt PNG"); - if (!_m3dstbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; - if (!_m3dstbi__zbuild_huffman(&a->z_distance, lencodes + hlit, hdist)) return 0; - return 1; -} - -_inline static int _m3dstbi__parse_uncompressed_block(_m3dstbi__zbuf *a) { - unsigned char header[4]; - int len, nlen, k; - if (a->num_bits & 7) - _m3dstbi__zreceive(a, a->num_bits & 7); - k = 0; - while (a->num_bits > 0) { - header[k++] = (unsigned char)(a->code_buffer & 255); - a->code_buffer >>= 8; - a->num_bits -= 8; - } - STBI_ASSERT(a->num_bits == 0); - while (k < 4) - header[k++] = _m3dstbi__zget8(a); - len = header[1] * 256 + header[0]; - nlen = header[3] * 256 + header[2]; - if (nlen != (len ^ 0xffff)) return _m3dstbi__err("zlib corrupt", "Corrupt PNG"); - if (a->zbuffer + len > a->zbuffer_end) return _m3dstbi__err("read past buffer", "Corrupt PNG"); - if (a->zout + len > a->zout_end) - if (!_m3dstbi__zexpand(a, a->zout, len)) return 0; - memcpy(a->zout, a->zbuffer, len); - a->zbuffer += len; - a->zout += len; - return 1; -} - -static int _m3dstbi__parse_zlib_header(_m3dstbi__zbuf *a) { - int cmf = _m3dstbi__zget8(a); - int cm = cmf & 15; - /* int cinfo = cmf >> 4; */ - int flg = _m3dstbi__zget8(a); - if ((cmf * 256 + flg) % 31 != 0) return _m3dstbi__err("bad zlib header", "Corrupt PNG"); - if (flg & 32) return _m3dstbi__err("no preset dict", "Corrupt PNG"); - if (cm != 8) return _m3dstbi__err("bad compression", "Corrupt PNG"); - return 1; -} - -static unsigned char _m3dstbi__zdefault_length[288], _m3dstbi__zdefault_distance[32]; -static void _m3dstbi__init_zdefaults(void) { - int i; - for (i = 0; i <= 143; ++i) - _m3dstbi__zdefault_length[i] = 8; - for (; i <= 255; ++i) - _m3dstbi__zdefault_length[i] = 9; - for (; i <= 279; ++i) - _m3dstbi__zdefault_length[i] = 7; - for (; i <= 287; ++i) - _m3dstbi__zdefault_length[i] = 8; - - for (i = 0; i <= 31; ++i) - _m3dstbi__zdefault_distance[i] = 5; -} - -static int _m3dstbi__parse_zlib(_m3dstbi__zbuf *a, int parse_header) { - int final, type; - if (parse_header) - if (!_m3dstbi__parse_zlib_header(a)) return 0; - a->num_bits = 0; - a->code_buffer = 0; - do { - final = _m3dstbi__zreceive(a, 1); - type = _m3dstbi__zreceive(a, 2); - if (type == 0) { - if (!_m3dstbi__parse_uncompressed_block(a)) return 0; - } else if (type == 3) { - return 0; - } else { - if (type == 1) { - if (!_m3dstbi__zbuild_huffman(&a->z_length, _m3dstbi__zdefault_length, 288)) return 0; - if (!_m3dstbi__zbuild_huffman(&a->z_distance, _m3dstbi__zdefault_distance, 32)) return 0; - } else { - if (!_m3dstbi__compute_huffman_codes(a)) return 0; - } - if (!_m3dstbi__parse_huffman_block(a)) return 0; - } - } while (!final); - return 1; -} - -static int _m3dstbi__do_zlib(_m3dstbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) { - a->zout_start = obuf; - a->zout = obuf; - a->zout_end = obuf + olen; - a->z_expandable = exp; - _m3dstbi__init_zdefaults(); - return _m3dstbi__parse_zlib(a, parse_header); -} - -char *_m3dstbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) { - _m3dstbi__zbuf a; - char *p = (char *)_m3dstbi__malloc(initial_size); - if (p == NULL) return NULL; - a.zbuffer = (unsigned char *)buffer; - a.zbuffer_end = (unsigned char *)buffer + len; - if (_m3dstbi__do_zlib(&a, p, initial_size, 1, parse_header)) { - if (outlen) *outlen = (int)(a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -typedef struct -{ - _m3dstbi__uint32 length; - _m3dstbi__uint32 type; -} _m3dstbi__pngchunk; - -static _m3dstbi__pngchunk _m3dstbi__get_chunk_header(_m3dstbi__context *s) { - _m3dstbi__pngchunk c; - c.length = _m3dstbi__get32be(s); - c.type = _m3dstbi__get32be(s); - return c; -} - -_inline static int _m3dstbi__check_png_header(_m3dstbi__context *s) { - static unsigned char png_sig[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; - int i; - for (i = 0; i < 8; ++i) - if (_m3dstbi__get8(s) != png_sig[i]) return _m3dstbi__err("bad png sig", "Not a PNG"); - return 1; -} - -typedef struct -{ - _m3dstbi__context *s; - unsigned char *idata, *expanded, *out; - int depth; -} _m3dstbi__png; - -enum { - STBI__F_none = 0, - STBI__F_sub = 1, - STBI__F_up = 2, - STBI__F_avg = 3, - STBI__F_paeth = 4, - STBI__F_avg_first, - STBI__F_paeth_first -}; - -static unsigned char first_row_filter[5] = { - STBI__F_none, - STBI__F_sub, - STBI__F_none, - STBI__F_avg_first, - STBI__F_paeth_first -}; - -static int _m3dstbi__paeth(int a, int b, int c) { - int p = a + b - c; - int pa = abs(p - a); - int pb = abs(p - b); - int pc = abs(p - c); - if (pa <= pb && pa <= pc) return a; - if (pb <= pc) return b; - return c; -} - -static unsigned char _m3dstbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0, 0, 0, 0x01 }; - -static int _m3dstbi__create_png_image_raw(_m3dstbi__png *a, unsigned char *raw, _m3dstbi__uint32 raw_len, int out_n, _m3dstbi__uint32 x, _m3dstbi__uint32 y, int depth, int color) { - int bytes = (depth == 16 ? 2 : 1); - _m3dstbi__context *s = a->s; - _m3dstbi__uint32 i, j, stride = x * out_n * bytes; - _m3dstbi__uint32 img_len, img_width_bytes; - int k; - int img_n = s->img_n; - - int output_bytes = out_n * bytes; - int filter_bytes = img_n * bytes; - int width = x; - - STBI_ASSERT(out_n == s->img_n || out_n == s->img_n + 1); - a->out = (unsigned char *)_m3dstbi__malloc_mad3(x, y, output_bytes, 0); - if (!a->out) return _m3dstbi__err("outofmem", "Out of memory"); - - if (!_m3dstbi__mad3sizes_valid(img_n, x, depth, 7)) return _m3dstbi__err("too large", "Corrupt PNG"); - img_width_bytes = (((img_n * x * depth) + 7) >> 3); - img_len = (img_width_bytes + 1) * y; - if (s->img_x == x && s->img_y == y) { - if (raw_len != img_len) return _m3dstbi__err("not enough pixels", "Corrupt PNG"); - } else { - if (raw_len < img_len) return _m3dstbi__err("not enough pixels", "Corrupt PNG"); - } - - for (j = 0; j < y; ++j) { - unsigned char *cur = a->out + stride * j; - unsigned char *prior = cur - stride; - int filter = *raw++; - - if (filter > 4) - return _m3dstbi__err("invalid filter", "Corrupt PNG"); - - if (depth < 8) { - STBI_ASSERT(img_width_bytes <= x); - cur += x * out_n - img_width_bytes; - filter_bytes = 1; - width = img_width_bytes; - } - prior = cur - stride; - - if (j == 0) filter = first_row_filter[filter]; - - for (k = 0; k < filter_bytes; ++k) { - switch (filter) { - case STBI__F_none: cur[k] = raw[k]; break; - case STBI__F_sub: cur[k] = raw[k]; break; - case STBI__F_up: cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; - case STBI__F_avg: cur[k] = STBI__BYTECAST(raw[k] + (prior[k] >> 1)); break; - case STBI__F_paeth: cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(0, prior[k], 0)); break; - case STBI__F_avg_first: cur[k] = raw[k]; break; - case STBI__F_paeth_first: cur[k] = raw[k]; break; - } - } - - if (depth == 8) { - if (img_n != out_n) - cur[img_n] = 255; - raw += img_n; - cur += out_n; - prior += out_n; - } else if (depth == 16) { - if (img_n != out_n) { - cur[filter_bytes] = 255; - cur[filter_bytes + 1] = 255; - } - raw += filter_bytes; - cur += output_bytes; - prior += output_bytes; - } else { - raw += 1; - cur += 1; - prior += 1; - } - - if (depth < 8 || img_n == out_n) { - int nk = (width - 1) * filter_bytes; -#define STBI__CASE(f) \ - case f: \ - for (k = 0; k < nk; ++k) - switch (filter) { - case STBI__F_none: - memcpy(cur, raw, nk); - break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k - filter_bytes]); } - break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } - break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k - filter_bytes]) >> 1)); } - break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k - filter_bytes], prior[k], prior[k - filter_bytes])); } - break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k - filter_bytes] >> 1)); } - break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k - filter_bytes], 0, 0)); } - break; - } -#undef STBI__CASE - raw += nk; - } else { - STBI_ASSERT(img_n + 1 == out_n); -#define STBI__CASE(f) \ - case f: \ - for (i = x - 1; i >= 1; --i, cur[filter_bytes] = 255, raw += filter_bytes, cur += output_bytes, prior += output_bytes) \ - for (k = 0; k < filter_bytes; ++k) - switch (filter) { - STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } - break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k - output_bytes]); } - break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } - break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k - output_bytes]) >> 1)); } - break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k - output_bytes], prior[k], prior[k - output_bytes])); } - break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k - output_bytes] >> 1)); } - break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k - output_bytes], 0, 0)); } - break; - } -#undef STBI__CASE - - if (depth == 16) { - cur = a->out + stride * j; - for (i = 0; i < x; ++i, cur += output_bytes) { - cur[filter_bytes + 1] = 255; - } - } - } - } - - if (depth < 8) { - for (j = 0; j < y; ++j) { - unsigned char *cur = a->out + stride * j; - unsigned char *in = a->out + stride * j + x * out_n - img_width_bytes; - unsigned char scale = (color == 0) ? _m3dstbi__depth_scale_table[depth] : 1; - - if (depth == 4) { - for (k = x * img_n; k >= 2; k -= 2, ++in) { - *cur++ = scale * ((*in >> 4)); - *cur++ = scale * ((*in) & 0x0f); - } - if (k > 0) *cur++ = scale * ((*in >> 4)); - } else if (depth == 2) { - for (k = x * img_n; k >= 4; k -= 4, ++in) { - *cur++ = scale * ((*in >> 6)); - *cur++ = scale * ((*in >> 4) & 0x03); - *cur++ = scale * ((*in >> 2) & 0x03); - *cur++ = scale * ((*in) & 0x03); - } - if (k > 0) *cur++ = scale * ((*in >> 6)); - if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); - if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); - } else if (depth == 1) { - for (k = x * img_n; k >= 8; k -= 8, ++in) { - *cur++ = scale * ((*in >> 7)); - *cur++ = scale * ((*in >> 6) & 0x01); - *cur++ = scale * ((*in >> 5) & 0x01); - *cur++ = scale * ((*in >> 4) & 0x01); - *cur++ = scale * ((*in >> 3) & 0x01); - *cur++ = scale * ((*in >> 2) & 0x01); - *cur++ = scale * ((*in >> 1) & 0x01); - *cur++ = scale * ((*in) & 0x01); - } - if (k > 0) *cur++ = scale * ((*in >> 7)); - if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); - if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); - if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); - if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); - if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); - if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); - } - if (img_n != out_n) { - int q; - cur = a->out + stride * j; - if (img_n == 1) { - for (q = x - 1; q >= 0; --q) { - cur[q * 2 + 1] = 255; - cur[q * 2 + 0] = cur[q]; - } - } else { - STBI_ASSERT(img_n == 3); - for (q = x - 1; q >= 0; --q) { - cur[q * 4 + 3] = 255; - cur[q * 4 + 2] = cur[q * 3 + 2]; - cur[q * 4 + 1] = cur[q * 3 + 1]; - cur[q * 4 + 0] = cur[q * 3 + 0]; - } - } - } - } - } else if (depth == 16) { - unsigned char *cur = a->out; - _m3dstbi__uint16 *cur16 = (_m3dstbi__uint16 *)cur; - - for (i = 0; i < x * y * out_n; ++i, cur16++, cur += 2) { - *cur16 = (cur[0] << 8) | cur[1]; - } - } - - return 1; -} - -static int _m3dstbi__create_png_image(_m3dstbi__png *a, unsigned char *image_data, _m3dstbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) { - int bytes = (depth == 16 ? 2 : 1); - int out_bytes = out_n * bytes; - unsigned char *final; - int p; - if (!interlaced) - return _m3dstbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); - - final = (unsigned char *)_m3dstbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); - for (p = 0; p < 7; ++p) { - int xorig[] = { 0, 4, 0, 2, 0, 1, 0 }; - int yorig[] = { 0, 0, 4, 0, 2, 0, 1 }; - int xspc[] = { 8, 8, 4, 4, 2, 2, 1 }; - int yspc[] = { 8, 8, 8, 4, 4, 2, 2 }; - int i, j, x, y; - x = (a->s->img_x - xorig[p] + xspc[p] - 1) / xspc[p]; - y = (a->s->img_y - yorig[p] + yspc[p] - 1) / yspc[p]; - if (x && y) { - _m3dstbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; - if (!_m3dstbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { - STBI_FREE(final); - return 0; - } - for (j = 0; j < y; ++j) { - for (i = 0; i < x; ++i) { - int out_y = j * yspc[p] + yorig[p]; - int out_x = i * xspc[p] + xorig[p]; - memcpy(final + out_y * a->s->img_x * out_bytes + out_x * out_bytes, - a->out + (j * x + i) * out_bytes, out_bytes); - } - } - STBI_FREE(a->out); - image_data += img_len; - image_data_len -= img_len; - } - } - a->out = final; - - return 1; -} - -static int _m3dstbi__compute_transparency(_m3dstbi__png *z, unsigned char* tc, int out_n) { - _m3dstbi__context *s = z->s; - _m3dstbi__uint32 i, pixel_count = s->img_x * s->img_y; - unsigned char *p = z->out; - - STBI_ASSERT(out_n == 2 || out_n == 4); - - if (out_n == 2) { - for (i = 0; i < pixel_count; ++i) { - p[1] = (p[0] == tc[0] ? 0 : 255); - p += 2; - } - } else { - for (i = 0; i < pixel_count; ++i) { - if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) - p[3] = 0; - p += 4; - } - } - return 1; -} - -static int _m3dstbi__compute_transparency16(_m3dstbi__png *z, _m3dstbi__uint16 tc[3], int out_n) { - _m3dstbi__context *s = z->s; - _m3dstbi__uint32 i, pixel_count = s->img_x * s->img_y; - _m3dstbi__uint16 *p = (_m3dstbi__uint16 *)z->out; - - STBI_ASSERT(out_n == 2 || out_n == 4); - - if (out_n == 2) { - for (i = 0; i < pixel_count; ++i) { - p[1] = (p[0] == tc[0] ? 0 : 65535); - p += 2; - } - } else { - for (i = 0; i < pixel_count; ++i) { - if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) - p[3] = 0; - p += 4; - } - } - return 1; -} - -static int _m3dstbi__expand_png_palette(_m3dstbi__png *a, unsigned char *palette, int len, int pal_img_n) { - _m3dstbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; - unsigned char *p, *temp_out, *orig = a->out; - - p = (unsigned char *)_m3dstbi__malloc_mad2(pixel_count, pal_img_n, 0); - if (p == NULL) return _m3dstbi__err("outofmem", "Out of memory"); - - temp_out = p; - - if (pal_img_n == 3) { - for (i = 0; i < pixel_count; ++i) { - int n = orig[i] * 4; - p[0] = palette[n]; - p[1] = palette[n + 1]; - p[2] = palette[n + 2]; - p += 3; - } - } else { - for (i = 0; i < pixel_count; ++i) { - int n = orig[i] * 4; - p[0] = palette[n]; - p[1] = palette[n + 1]; - p[2] = palette[n + 2]; - p[3] = palette[n + 3]; - p += 4; - } - } - STBI_FREE(a->out); - a->out = temp_out; - - STBI_NOTUSED(len); - - return 1; -} - -#define STBI__PNG_TYPE(a, b, c, d) (((unsigned)(a) << 24) + ((unsigned)(b) << 16) + ((unsigned)(c) << 8) + (unsigned)(d)) - -static int _m3dstbi__parse_png_file(_m3dstbi__png *z, int scan, int req_comp) { - unsigned char palette[1024], pal_img_n = 0; - unsigned char has_trans = 0, tc[3] = {}; - _m3dstbi__uint16 tc16[3] = {}; - _m3dstbi__uint32 ioff = 0, idata_limit = 0, i, pal_len = 0; - int first = 1, k, interlace = 0, color = 0; - _m3dstbi__context *s = z->s; - - z->expanded = NULL; - z->idata = NULL; - z->out = NULL; - - if (!_m3dstbi__check_png_header(s)) return 0; - - if (scan == STBI__SCAN_type) return 1; - - for (;;) { - _m3dstbi__pngchunk c = _m3dstbi__get_chunk_header(s); - switch (c.type) { - case STBI__PNG_TYPE('C', 'g', 'B', 'I'): - _m3dstbi__skip(s, c.length); - break; - case STBI__PNG_TYPE('I', 'H', 'D', 'R'): { - int comp, filter; - if (!first) return _m3dstbi__err("multiple IHDR", "Corrupt PNG"); - first = 0; - if (c.length != 13) return _m3dstbi__err("bad IHDR len", "Corrupt PNG"); - s->img_x = _m3dstbi__get32be(s); - if (s->img_x > (1 << 24)) return _m3dstbi__err("too large", "Very large image (corrupt?)"); - s->img_y = _m3dstbi__get32be(s); - if (s->img_y > (1 << 24)) return _m3dstbi__err("too large", "Very large image (corrupt?)"); - z->depth = _m3dstbi__get8(s); - if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return _m3dstbi__err("1/2/4/8/16-bit only", "PNG not supported: 1/2/4/8/16-bit only"); - color = _m3dstbi__get8(s); - if (color > 6) return _m3dstbi__err("bad ctype", "Corrupt PNG"); - if (color == 3 && z->depth == 16) return _m3dstbi__err("bad ctype", "Corrupt PNG"); - if (color == 3) - pal_img_n = 3; - else if (color & 1) - return _m3dstbi__err("bad ctype", "Corrupt PNG"); - comp = _m3dstbi__get8(s); - if (comp) return _m3dstbi__err("bad comp method", "Corrupt PNG"); - filter = _m3dstbi__get8(s); - if (filter) return _m3dstbi__err("bad filter method", "Corrupt PNG"); - interlace = _m3dstbi__get8(s); - if (interlace > 1) return _m3dstbi__err("bad interlace method", "Corrupt PNG"); - if (!s->img_x || !s->img_y) return _m3dstbi__err("0-pixel image", "Corrupt PNG"); - if (!pal_img_n) { - s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); - if ((1 << 30) / s->img_x / s->img_n < s->img_y) return _m3dstbi__err("too large", "Image too large to decode"); - if (scan == STBI__SCAN_header) return 1; - } else { - s->img_n = 1; - if ((1 << 30) / s->img_x / 4 < s->img_y) return _m3dstbi__err("too large", "Corrupt PNG"); - } - break; - } - - case STBI__PNG_TYPE('P', 'L', 'T', 'E'): { - if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); - if (c.length > 256 * 3) return _m3dstbi__err("invalid PLTE", "Corrupt PNG"); - pal_len = c.length / 3; - if (pal_len * 3 != c.length) return _m3dstbi__err("invalid PLTE", "Corrupt PNG"); - for (i = 0; i < pal_len; ++i) { - palette[i * 4 + 0] = _m3dstbi__get8(s); - palette[i * 4 + 1] = _m3dstbi__get8(s); - palette[i * 4 + 2] = _m3dstbi__get8(s); - palette[i * 4 + 3] = 255; - } - break; - } - - case STBI__PNG_TYPE('t', 'R', 'N', 'S'): { - if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); - if (z->idata) return _m3dstbi__err("tRNS after IDAT", "Corrupt PNG"); - if (pal_img_n) { - if (scan == STBI__SCAN_header) { - s->img_n = 4; - return 1; - } - if (pal_len == 0) return _m3dstbi__err("tRNS before PLTE", "Corrupt PNG"); - if (c.length > pal_len) return _m3dstbi__err("bad tRNS len", "Corrupt PNG"); - pal_img_n = 4; - for (i = 0; i < c.length; ++i) - palette[i * 4 + 3] = _m3dstbi__get8(s); - } else { - if (!(s->img_n & 1)) return _m3dstbi__err("tRNS with alpha", "Corrupt PNG"); - if (c.length != (_m3dstbi__uint32)s->img_n * 2) return _m3dstbi__err("bad tRNS len", "Corrupt PNG"); - has_trans = 1; - if (z->depth == 16) { - for (k = 0; k < s->img_n; ++k) - tc16[k] = (_m3dstbi__uint16)_m3dstbi__get16be(s); - } else { - for (k = 0; k < s->img_n; ++k) - tc[k] = (unsigned char)(_m3dstbi__get16be(s) & 255) * _m3dstbi__depth_scale_table[z->depth]; - } - } - break; - } - - case STBI__PNG_TYPE('I', 'D', 'A', 'T'): { - if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); - if (pal_img_n && !pal_len) return _m3dstbi__err("no PLTE", "Corrupt PNG"); - if (scan == STBI__SCAN_header) { - s->img_n = pal_img_n; - return 1; - } - if ((int)(ioff + c.length) < (int)ioff) return 0; - if (ioff + c.length > idata_limit) { - _m3dstbi__uint32 idata_limit_old = idata_limit; - unsigned char *p; - if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; - while (ioff + c.length > idata_limit) - idata_limit *= 2; - STBI_NOTUSED(idata_limit_old); - p = (unsigned char *)STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); - if (p == NULL) return _m3dstbi__err("outofmem", "Out of memory"); - z->idata = p; - } - if (!_m3dstbi__getn(s, z->idata + ioff, c.length)) return _m3dstbi__err("outofdata", "Corrupt PNG"); - ioff += c.length; - break; - } - - case STBI__PNG_TYPE('I', 'E', 'N', 'D'): { - _m3dstbi__uint32 raw_len, bpl; - if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); - if (scan != STBI__SCAN_load) return 1; - if (z->idata == NULL) return _m3dstbi__err("no IDAT", "Corrupt PNG"); - bpl = (s->img_x * z->depth + 7) / 8; - raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; - z->expanded = (unsigned char *)_m3dstbi_zlib_decode_malloc_guesssize_headerflag((char *)z->idata, ioff, raw_len, (int *)&raw_len, 1); - if (z->expanded == NULL) return 0; - STBI_FREE(z->idata); - z->idata = NULL; - if ((req_comp == s->img_n + 1 && req_comp != 3 && !pal_img_n) || has_trans) - s->img_out_n = s->img_n + 1; - else - s->img_out_n = s->img_n; - if (!_m3dstbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; - if (has_trans) { - if (z->depth == 16) { - if (!_m3dstbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; - } else { - if (!_m3dstbi__compute_transparency(z, tc, s->img_out_n)) return 0; - } - } - if (pal_img_n) { - s->img_n = pal_img_n; - s->img_out_n = pal_img_n; - if (req_comp >= 3) s->img_out_n = req_comp; - if (!_m3dstbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) - return 0; - } else if (has_trans) { - ++s->img_n; - } - STBI_FREE(z->expanded); - z->expanded = NULL; - return 1; - } - - default: - if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); - if ((c.type & (1 << 29)) == 0) { - return _m3dstbi__err("invalid_chunk", "PNG not supported: unknown PNG chunk type"); - } - _m3dstbi__skip(s, c.length); - break; - } - _m3dstbi__get32be(s); - } -} - -static void *_m3dstbi__do_png(_m3dstbi__png *p, int *x, int *y, int *n, int req_comp, _m3dstbi__result_info *ri) { - void *result = NULL; - if (req_comp < 0 || req_comp > 4) { - _m3dstbi__err("bad req_comp", "Internal error"); - return NULL; - } - if (_m3dstbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { - if (p->depth < 8) - ri->bits_per_channel = 8; - else - ri->bits_per_channel = p->depth; - result = p->out; - p->out = NULL; - if (req_comp && req_comp != p->s->img_out_n) { - if (ri->bits_per_channel == 8) - result = _m3dstbi__convert_format((unsigned char *)result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); - else - result = _m3dstbi__convert_format16((_m3dstbi__uint16 *)result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); - p->s->img_out_n = req_comp; - if (result == NULL) return result; - } - *x = p->s->img_x; - *y = p->s->img_y; - if (n) *n = p->s->img_n; - } - STBI_FREE(p->out); - p->out = NULL; - STBI_FREE(p->expanded); - p->expanded = NULL; - STBI_FREE(p->idata); - p->idata = NULL; - - return result; -} - -static void *_m3dstbi__png_load(_m3dstbi__context *s, int *x, int *y, int *comp, int req_comp, _m3dstbi__result_info *ri) { - _m3dstbi__png p; - p.s = s; - return _m3dstbi__do_png(&p, x, y, comp, req_comp, ri); -} -#define stbi__context _m3dstbi__context -#define stbi__result_info _m3dstbi__result_info -#define stbi__png_load _m3dstbi__png_load -#define stbi_zlib_decode_malloc_guesssize_headerflag _m3dstbi_zlib_decode_malloc_guesssize_headerflag -#endif - #if defined(M3D_EXPORTER) && !defined(INCLUDE_STB_IMAGE_WRITE_H) /* zlib_compressor from @@ -2053,7 +829,7 @@ unsigned char *_m3dstbi_zlib_compress(unsigned char *data, int data_len, int *ou #include #endif -#if !defined(M3D_NOIMPORTER) +#if !defined(M3D_NOIMPORTER) /* helper functions for the ASCII parser */ static char *_m3d_findarg(char *s) { while (s && *s && *s != ' ' && *s != '\t' && *s != '\r' && *s != '\n') @@ -5740,7 +4516,7 @@ unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size } if (length) { uint32_t v = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); - memcpy( length, &v, sizeof(uint32_t)); + memcpy( length, &v, sizeof(uint32_t)); len += v; } out = NULL; @@ -5772,7 +4548,7 @@ unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size } } uint32_t v = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len)); - memcpy( length, &v, sizeof(uint32_t)); + memcpy( length, &v, sizeof(uint32_t)); len += v; out = NULL; } diff --git a/code/AssetLib/MD5/MD5Loader.cpp b/code/AssetLib/MD5/MD5Loader.cpp index 0d9c77b71..9fba60c61 100644 --- a/code/AssetLib/MD5/MD5Loader.cpp +++ b/code/AssetLib/MD5/MD5Loader.cpp @@ -485,7 +485,7 @@ void MD5Importer::LoadMD5MeshFile() { } MD5::WeightDesc &weightDesc = meshSrc.mWeights[w]; - if (weightDesc.mWeight < AI_MD5_WEIGHT_EPSILON && weightDesc.mWeight >= -AI_MD5_WEIGHT_EPSILON) { + if (weightDesc.mWeight < AI_MD5_WEIGHT_EPSILON && weightDesc.mWeight >= -AI_MD5_WEIGHT_EPSILON) { continue; } diff --git a/code/AssetLib/MDC/MDCFileData.h b/code/AssetLib/MDC/MDCFileData.h index 616556f03..a8f3aea43 100644 --- a/code/AssetLib/MDC/MDCFileData.h +++ b/code/AssetLib/MDC/MDCFileData.h @@ -120,13 +120,13 @@ struct Surface { , ulFlags() , ulNumCompFrames() , ulNumBaseFrames() - , ulNumShaders() + , ulNumShaders() , ulNumVertices() , ulNumTriangles() , ulOffsetTriangles() , ulOffsetShaders() , ulOffsetTexCoords() - , ulOffsetBaseVerts() + , ulOffsetBaseVerts() , ulOffsetCompVerts() , ulOffsetFrameBaseFrames() , ulOffsetFrameCompFrames() diff --git a/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp b/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp index e6576b344..1ff86fe27 100644 --- a/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp +++ b/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp @@ -629,7 +629,7 @@ void HL1MDLLoader::read_meshes() { +-- bodypart --+-- model -- [mesh index, mesh index, ...] | | | +-- model -- [mesh index, mesh index, ...] - | | + | | | ... | |-- bodypart -- ... @@ -1298,7 +1298,7 @@ void HL1MDLLoader::read_global_info() { * @note The structure of this method is taken from HL2 source code. * Although this is from HL2, it's implementation is almost identical * to code found in HL1 SDK. See HL1 and HL2 SDKs for more info. -* +* * source: * HL1 source code. * file: studio_render.cpp diff --git a/code/AssetLib/MMD/MMDPmxParser.cpp b/code/AssetLib/MMD/MMDPmxParser.cpp index d57dc169a..be3b10248 100644 --- a/code/AssetLib/MMD/MMDPmxParser.cpp +++ b/code/AssetLib/MMD/MMDPmxParser.cpp @@ -102,7 +102,7 @@ namespace pmx const unsigned int targetSize = size * 3; // enough to encode char *targetStart = new char[targetSize]; std::memset(targetStart, 0, targetSize * sizeof(char)); - + utf8::utf16to8( sourceStart, sourceStart + size/2, targetStart ); std::string result(targetStart); @@ -516,13 +516,13 @@ namespace pmx stream->read((char*) magic, sizeof(char) * 4); if (magic[0] != 0x50 || magic[1] != 0x4d || magic[2] != 0x58 || magic[3] != 0x20) { - throw DeadlyImportError("MMD: Invalid magic number."); - } + throw DeadlyImportError("MMD: Invalid magic number."); + } stream->read((char*) &version, sizeof(float)); if (version != 2.0f && version != 2.1f) { throw DeadlyImportError("MMD: Unsupported version (must be 2.0 or 2.1): ", ai_to_string(version)); - } + } this->setting.Read(stream); this->model_name = ReadString(stream, setting.encoding); diff --git a/code/AssetLib/OFF/OFFLoader.cpp b/code/AssetLib/OFF/OFFLoader.cpp index 360ffc51b..fb8f3b424 100644 --- a/code/AssetLib/OFF/OFFLoader.cpp +++ b/code/AssetLib/OFF/OFFLoader.cpp @@ -138,7 +138,7 @@ void OFFImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS const char* car = buffer; const char* end = buffer + mBuffer2.size(); NextToken(&car, end); - + if (car < end - 2 && car[0] == 'S' && car[1] == 'T') { hasTexCoord = true; car += 2; } @@ -164,7 +164,7 @@ void OFFImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS dimensions = 3; hasHomogenous = false; NextToken(&car, end); - + // at this point the next token should be an integer number if (car >= end - 1 || *car < '0' || *car > '9') { throw DeadlyImportError("OFF: Header is invalid"); @@ -223,7 +223,7 @@ void OFFImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS ASSIMP_LOG_ERROR("OFF: The number of verts in the header is incorrect"); break; } - aiVector3D& v = mesh->mVertices[i]; + aiVector3D& v = mesh->mVertices[i]; sz = line; // helper array to write a for loop over possible dimension values @@ -255,7 +255,7 @@ void OFFImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS SkipSpaces(&sz); fast_atoreal_move(sz,(ai_real&)n.z); } - + // reading colors is a pain because the specification says it can be // integers or floats, and any number of them between 1 and 4 included, // until the next comment or end of line @@ -321,7 +321,7 @@ void OFFImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS ++i; ++faces; } - + // generate the output node graph pScene->mRootNode = new aiNode(); pScene->mRootNode->mName.Set(""); diff --git a/code/AssetLib/Obj/ObjExporter.h b/code/AssetLib/Obj/ObjExporter.h index 3a46da780..a64f38f74 100644 --- a/code/AssetLib/Obj/ObjExporter.h +++ b/code/AssetLib/Obj/ObjExporter.h @@ -67,7 +67,7 @@ public: ~ObjExporter(); std::string GetMaterialLibName(); std::string GetMaterialLibFileName(); - + /// public string-streams to write all output into std::ostringstream mOutput, mOutputMat; @@ -137,13 +137,13 @@ private: } }; - struct aiVectorCompare { - bool operator() (const aiVector3D& a, const aiVector3D& b) const { - if(a.x < b.x) return true; - if(a.x > b.x) return false; - if(a.y < b.y) return true; - if(a.y > b.y) return false; - if(a.z < b.z) return true; + struct aiVectorCompare { + bool operator() (const aiVector3D& a, const aiVector3D& b) const { + if(a.x < b.x) return true; + if(a.x > b.x) return false; + if(a.y < b.y) return true; + if(a.y > b.y) return false; + if(a.z < b.z) return true; return false; } }; @@ -153,7 +153,7 @@ private: int mNextIndex; typedef std::map dataType; dataType vecMap; - + public: indexMap() : mNextIndex(1) { diff --git a/code/AssetLib/Obj/ObjFileImporter.cpp b/code/AssetLib/Obj/ObjFileImporter.cpp index d6232be81..4e943fb5f 100644 --- a/code/AssetLib/Obj/ObjFileImporter.cpp +++ b/code/AssetLib/Obj/ObjFileImporter.cpp @@ -162,7 +162,7 @@ void ObjFileImporter::InternReadFile(const std::string &file, aiScene *pScene, I // ------------------------------------------------------------------------------------------------ // Create the data from parsed obj-file void ObjFileImporter::CreateDataFromImport(const ObjFile::Model *pModel, aiScene *pScene) { - if (0L == pModel) { + if (nullptr == pModel) { return; } @@ -468,7 +468,7 @@ void ObjFileImporter::createVertexArray(const ObjFile::Model *pModel, } // Copy all vertex colors - if (!pModel->m_VertexColors.empty()) { + if (vertex < pModel->m_VertexColors.size()) { const aiVector3D &color = pModel->m_VertexColors[vertex]; pMesh->mColors[0][newIndex] = aiColor4D(color.x, color.y, color.z, 1.0); } diff --git a/code/AssetLib/Obj/ObjFileMtlImporter.cpp b/code/AssetLib/Obj/ObjFileMtlImporter.cpp index bf1b70c90..94e57c26b 100644 --- a/code/AssetLib/Obj/ObjFileMtlImporter.cpp +++ b/code/AssetLib/Obj/ObjFileMtlImporter.cpp @@ -146,7 +146,7 @@ void ObjFileMtlImporter::load() { ++m_DataIt; ai_real d; getFloatValue(d); - m_pModel->m_pCurrentMaterial->alpha = static_cast(1.0) - d; + m_pModel->m_pCurrentMaterial->alpha = static_cast(1.0) - d; } m_DataIt = skipLine(m_DataIt, m_DataItEnd, m_uiLine); } break; diff --git a/code/AssetLib/Ogre/OgreMaterial.cpp b/code/AssetLib/Ogre/OgreMaterial.cpp index 295dedde6..6da82f89c 100644 --- a/code/AssetLib/Ogre/OgreMaterial.cpp +++ b/code/AssetLib/Ogre/OgreMaterial.cpp @@ -415,8 +415,8 @@ bool OgreImporter::ReadTextureUnit(const std::string &textureUnitName, stringstr // User defined Assimp config property to detect texture type from filename. if (m_detectTextureTypeFromFilename) { - size_t posSuffix = textureRef.find_last_of("."); - size_t posUnderscore = textureRef.find_last_of("_"); + size_t posSuffix = textureRef.find_last_of('.'); + size_t posUnderscore = textureRef.find_last_of('_'); if (posSuffix != string::npos && posUnderscore != string::npos && posSuffix > posUnderscore) { string identifier = ai_tolower(textureRef.substr(posUnderscore, posSuffix - posUnderscore)); diff --git a/code/AssetLib/Ply/PlyParser.cpp b/code/AssetLib/Ply/PlyParser.cpp index 59cb6b976..8af0edfdc 100644 --- a/code/AssetLib/Ply/PlyParser.cpp +++ b/code/AssetLib/Ply/PlyParser.cpp @@ -419,8 +419,7 @@ bool PLY::DOM::ParseHeader(IOStreamBuffer &streamBuffer, std::vector if (PLY::Element::ParseElement(streamBuffer, buffer, &out)) { // add the element to the list of elements alElements.push_back(out); - } else if ( TokenMatch(buffer, "end_header\r", 11) || //checks for header end with /r/n ending - TokenMatch(buffer, "end_header", 10)) { //checks for /n ending, if it doesn't end with /r/n + } else if (TokenMatch(buffer, "end_header", 10)) { //checks for /n ending, if it doesn't end with /r/n // we have reached the end of the header break; } else { @@ -501,6 +500,11 @@ bool PLY::DOM::ParseInstanceBinary(IOStreamBuffer &streamBuffer, DOM *p_pc } streamBuffer.getNextBlock(buffer); + + // remove first char if it's /n in case of file with /r/n + if (((char *)&buffer[0])[0] == '\n') + buffer.erase(buffer.begin(), buffer.begin() + 1); + unsigned int bufferSize = static_cast(buffer.size()); const char *pCur = (char *)&buffer[0]; if (!p_pcOut->ParseElementInstanceListsBinary(streamBuffer, buffer, pCur, bufferSize, loader, p_bBE)) { diff --git a/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp b/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp index becfa41fc..a1da8fd74 100644 --- a/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp +++ b/code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp @@ -99,7 +99,7 @@ static void extractIds(const std::string &key, int &id1, int &id2) { return; } - const std::string::size_type pos = key.find("."); + const std::string::size_type pos = key.find('.'); if (std::string::npos == pos) { return; } @@ -208,7 +208,7 @@ void Q3BSPFileImporter::separateMapName(const std::string &importName, std::stri return; } - const std::string::size_type pos = importName.rfind(","); + const std::string::size_type pos = importName.rfind(','); if (std::string::npos == pos) { archiveName = importName; return; diff --git a/code/AssetLib/SMD/SMDLoader.cpp b/code/AssetLib/SMD/SMDLoader.cpp index de9c65c4a..90f0b7697 100644 --- a/code/AssetLib/SMD/SMDLoader.cpp +++ b/code/AssetLib/SMD/SMDLoader.cpp @@ -438,7 +438,7 @@ void SMDImporter::AddBoneChildren(aiNode* pcNode, uint32_t iParent) { pc->mTransformation = bone.sAnim.asKeys[0].matrix; } - if (bone.iParent == static_cast(-1)) { + if (bone.iParent == static_cast(-1)) { bone.mOffsetMatrix = pc->mTransformation; } else { bone.mOffsetMatrix = asBones[bone.iParent].mOffsetMatrix * pc->mTransformation; diff --git a/code/AssetLib/STEPParser/STEPFileReader.cpp b/code/AssetLib/STEPParser/STEPFileReader.cpp index e97ea1e28..360277912 100644 --- a/code/AssetLib/STEPParser/STEPFileReader.cpp +++ b/code/AssetLib/STEPParser/STEPFileReader.cpp @@ -49,21 +49,22 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "STEPFileEncoding.h" #include #include -#include #include +#include +#include using namespace Assimp; namespace EXPRESS = STEP::EXPRESS; // ------------------------------------------------------------------------------------------------ -std::string AddLineNumber(const std::string& s,uint64_t line /*= LINE_NOT_SPECIFIED*/, const std::string& prefix = "") +std::string AddLineNumber(const std::string& s,uint64_t line /*= LINE_NOT_SPECIFIED*/, const std::string& prefix = std::string()) { return line == STEP::SyntaxError::LINE_NOT_SPECIFIED ? prefix+s : static_cast( (Formatter::format(),prefix,"(line ",line,") ",s) ); } // ------------------------------------------------------------------------------------------------ -std::string AddEntityID(const std::string& s,uint64_t entity /*= ENTITY_NOT_SPECIFIED*/, const std::string& prefix = "") +std::string AddEntityID(const std::string& s,uint64_t entity /*= ENTITY_NOT_SPECIFIED*/, const std::string& prefix = std::string()) { return entity == STEP::TypeError::ENTITY_NOT_SPECIFIED ? prefix+s : static_cast( (Formatter::format(),prefix,"(entity #",entity,") ",s)); } @@ -87,7 +88,7 @@ static const char *ISO_Token = "ISO-10303-21;"; static const char *FILE_SCHEMA_Token = "FILE_SCHEMA"; // ------------------------------------------------------------------------------------------------ STEP::DB* STEP::ReadFileHeader(std::shared_ptr stream) { - std::shared_ptr reader = std::shared_ptr(new StreamReaderLE(stream)); + std::shared_ptr reader = std::shared_ptr(new StreamReaderLE(std::move(stream))); std::unique_ptr db = std::unique_ptr(new STEP::DB(reader)); LineSplitter &splitter = db->GetSplitter(); diff --git a/code/AssetLib/STL/STLExporter.cpp b/code/AssetLib/STL/STLExporter.cpp index bd4d96c71..59c6148ee 100644 --- a/code/AssetLib/STL/STLExporter.cpp +++ b/code/AssetLib/STL/STLExporter.cpp @@ -69,7 +69,7 @@ void ExportSceneSTL(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene if (exporter.mOutput.fail()) { throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile)); } - + // we're still here - export successfully completed. Write the file. std::unique_ptr outfile (pIOSystem->Open(pFile,"wt")); if (outfile == nullptr) { @@ -88,7 +88,7 @@ void ExportSceneSTLBinary(const char* pFile,IOSystem* pIOSystem, const aiScene* if (exporter.mOutput.fail()) { throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile)); } - + // we're still here - export successfully completed. Write the file. std::unique_ptr outfile (pIOSystem->Open(pFile,"wb")); if (outfile == nullptr) { @@ -139,9 +139,9 @@ STLExporter::STLExporter(const char* _filename, const aiScene* pScene, bool expo if (exportPointClouds) { WritePointCloud("Assimp_Pointcloud", pScene ); return; - } + } - // Export the assimp mesh + // Export the assimp mesh const std::string name = "AssimpScene"; mOutput << SolidToken << " " << name << endl; for(unsigned int i = 0; i < pScene->mNumMeshes; ++i) { diff --git a/code/AssetLib/STL/STLLoader.cpp b/code/AssetLib/STL/STLLoader.cpp index 433fb14c7..8cfe63e0d 100644 --- a/code/AssetLib/STL/STLLoader.cpp +++ b/code/AssetLib/STL/STLLoader.cpp @@ -372,7 +372,7 @@ void STLImporter::LoadASCIIFile(aiNode *root) { pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; for (size_t i=0; imNumVertices; ++i ) { pMesh->mVertices[i].x = positionBuffer[i].x; - pMesh->mVertices[i].y = positionBuffer[i].y; + pMesh->mVertices[i].y = positionBuffer[i].y; pMesh->mVertices[i].z = positionBuffer[i].z; } positionBuffer.clear(); @@ -382,7 +382,7 @@ void STLImporter::LoadASCIIFile(aiNode *root) { pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; for (size_t i=0; imNumVertices; ++i ) { pMesh->mNormals[i].x = normalBuffer[i].x; - pMesh->mNormals[i].y = normalBuffer[i].y; + pMesh->mNormals[i].y = normalBuffer[i].y; pMesh->mNormals[i].z = normalBuffer[i].z; } normalBuffer.clear(); diff --git a/code/AssetLib/Step/STEPFile.h b/code/AssetLib/Step/STEPFile.h index 90eaef5f3..e09faad98 100644 --- a/code/AssetLib/Step/STEPFile.h +++ b/code/AssetLib/Step/STEPFile.h @@ -634,7 +634,7 @@ private: }; template -inline bool operator==(std::shared_ptr lo, T whatever) { +inline bool operator==(const std::shared_ptr &lo, T whatever) { return *lo == whatever; // XXX use std::forward if we have 0x } @@ -816,7 +816,7 @@ public: typedef std::pair RefMapRange; private: - DB(std::shared_ptr reader) : + DB(const std::shared_ptr &reader) : reader(reader), splitter(*reader, true, true), evaluated_count(), schema(nullptr) {} public: diff --git a/code/AssetLib/Step/StepExporter.cpp b/code/AssetLib/Step/StepExporter.cpp index dfe5bab67..1228c72ea 100644 --- a/code/AssetLib/Step/StepExporter.cpp +++ b/code/AssetLib/Step/StepExporter.cpp @@ -175,12 +175,11 @@ void StepExporter::WriteFile() fColor.b = 0.8f; int ind = 100; // the start index to be used - int faceEntryLen = 30; // number of entries for a triangle/face + std::vector faceEntryLen; // numbers of entries for a triangle/face // prepare unique (count triangles and vertices) VectorIndexUMap uniqueVerts; // use a map to reduce find complexity to log(n) VectorIndexUMap::iterator it; - int countFace = 0; for (unsigned int i=0; imNumMeshes; ++i) { @@ -189,7 +188,7 @@ void StepExporter::WriteFile() { aiFace* face = &(mesh->mFaces[j]); - if (face->mNumIndices == 3) countFace++; + if (face->mNumIndices >= 3) faceEntryLen.push_back(15 + 5 * face->mNumIndices); } for (unsigned int j=0; jmNumVertices; ++j) { @@ -218,10 +217,13 @@ void StepExporter::WriteFile() // write the top of data mOutput << "DATA" << endstr; mOutput << "#1=MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION(' ',("; - for (int i=0; imFaces[j]); - if (face->mNumIndices != 3) continue; + const int numIndices = face->mNumIndices; + if (numIndices < 3) continue; - aiVector3D* v1 = &(mesh->mVertices[face->mIndices[0]]); - aiVector3D* v2 = &(mesh->mVertices[face->mIndices[1]]); - aiVector3D* v3 = &(mesh->mVertices[face->mIndices[2]]); - aiVector3D dv12 = *v2 - *v1; - aiVector3D dv23 = *v3 - *v2; - aiVector3D dv31 = *v1 - *v3; - aiVector3D dv13 = *v3 - *v1; - dv12.Normalize(); - dv23.Normalize(); - dv31.Normalize(); - dv13.Normalize(); + std::vector pidArray(numIndices, -1); // vertex id + std::vector dvArray(numIndices); // edge dir + for (int k = 0; k < numIndices; ++k) + { + aiVector3D *v1 = &(mesh->mVertices[face->mIndices[k]]); + pidArray[k] = uniqueVerts.find(v1)->second; - aiVector3D dvY = dv12; - aiVector3D dvX = dvY ^ dv13; + aiVector3D *v2 = nullptr; + if (k + 1 == numIndices) + v2 = &(mesh->mVertices[face->mIndices[0]]); + else + v2 = &(mesh->mVertices[face->mIndices[k + 1]]); + dvArray[k] = *v2 - *v1; + dvArray[k].Normalize(); + } + + aiVector3D dvY = dvArray[1]; + aiVector3D dvX = dvY ^ dvArray[0]; dvX.Normalize(); - int pid1 = uniqueVerts.find(v1)->second; - int pid2 = uniqueVerts.find(v2)->second; - int pid3 = uniqueVerts.find(v3)->second; - // mean vertex color for the face if available if (mesh->HasVertexColors(0)) { @@ -339,35 +344,62 @@ void StepExporter::WriteFile() /* 2 directions of the plane */ mOutput << "#" << sid+9 << "=PLANE('',#" << sid+10 << ")" << endstr; - mOutput << "#" << sid+10 << "=AXIS2_PLACEMENT_3D('',#" << pid1 << ", #" << sid+11 << ",#" << sid+12 << ")" << endstr; + mOutput << "#" << sid+10 << "=AXIS2_PLACEMENT_3D('',#" << pidArray[0] << ",#" << sid+11 << ",#" << sid+12 << ")" << endstr; mOutput << "#" << sid + 11 << "=DIRECTION('',(" << dvX.x << "," << dvX.y << "," << dvX.z << "))" << endstr; mOutput << "#" << sid + 12 << "=DIRECTION('',(" << dvY.x << "," << dvY.y << "," << dvY.z << "))" << endstr; mOutput << "#" << sid+13 << "=FACE_BOUND('',#" << sid+14 << ",.T.)" << endstr; - mOutput << "#" << sid+14 << "=EDGE_LOOP('',(#" << sid+15 << ",#" << sid+16 << ",#" << sid+17 << "))" << endstr; + mOutput << "#" << sid+14 << "=EDGE_LOOP('',("; + int edgeLoopStart = sid + 15; + for (int k = 0; k < numIndices; ++k) + { + if (k == 0) + mOutput << "#"; + else + mOutput << ",#"; + mOutput << edgeLoopStart + k; + } + mOutput << "))" << endstr; /* edge loop */ - mOutput << "#" << sid+15 << "=ORIENTED_EDGE('',*,*,#" << sid+18 << ",.T.)" << endstr; - mOutput << "#" << sid+16 << "=ORIENTED_EDGE('',*,*,#" << sid+19 << ",.T.)" << endstr; - mOutput << "#" << sid+17 << "=ORIENTED_EDGE('',*,*,#" << sid+20 << ",.T.)" << endstr; + int orientedEdgesStart = edgeLoopStart + numIndices; + for (int k=0; k < numIndices; k++) + { + mOutput << "#" << edgeLoopStart+k << "=ORIENTED_EDGE('',*,*,#" << orientedEdgesStart + k << ",.T.)" << endstr; + } /* oriented edges */ - mOutput << "#" << sid+18 << "=EDGE_CURVE('',#" << pid1+1 << ",#" << pid2+1 << ",#" << sid+21 << ",.F.)" << endstr; - mOutput << "#" << sid+19 << "=EDGE_CURVE('',#" << pid2+1 << ",#" << pid3+1 << ",#" << sid+22 << ",.T.)" << endstr; - mOutput << "#" << sid+20 << "=EDGE_CURVE('',#" << pid3+1 << ",#" << pid1+1 << ",#" << sid+23 << ",.T.)" << endstr; + int lineStart = orientedEdgesStart + numIndices; + for (int k=0; k < numIndices; ++k) + { + if (k == 0) + mOutput << "#" << orientedEdgesStart+k << "=EDGE_CURVE('',#" << pidArray[k]+1 << ",#" << pidArray[k+1]+1 << ",#" << lineStart+k << ",.F.)" << endstr; + else if (k+1 == numIndices) + mOutput << "#" << orientedEdgesStart+k << "=EDGE_CURVE('',#" << pidArray[k]+1 << ",#" << pidArray[0]+1 << ",#" << lineStart+k << ",.T.)" << endstr; + else + mOutput << "#" << orientedEdgesStart+k << "=EDGE_CURVE('',#" << pidArray[k]+1 << ",#" << pidArray[k+1]+1 << ",#" << lineStart+k << ",.T.)" << endstr; + } - /* 3 lines and 3 vectors for the lines for the 3 edge curves */ - mOutput << "#" << sid+21 << "=LINE('',#" << pid1 << ",#" << sid+24 << ")" << endstr; - mOutput << "#" << sid+22 << "=LINE('',#" << pid2 << ",#" << sid+25 << ")" << endstr; - mOutput << "#" << sid+23 << "=LINE('',#" << pid3 << ",#" << sid+26 << ")" << endstr; - mOutput << "#" << sid+24 << "=VECTOR('',#" << sid+27 << ",1.0)" << endstr; - mOutput << "#" << sid+25 << "=VECTOR('',#" << sid+28 << ",1.0)" << endstr; - mOutput << "#" << sid+26 << "=VECTOR('',#" << sid+29 << ",1.0)" << endstr; - mOutput << "#" << sid+27 << "=DIRECTION('',(" << dv12.x << "," << dv12.y << "," << dv12.z << "))" << endstr; - mOutput << "#" << sid+28 << "=DIRECTION('',(" << dv23.x << "," << dv23.y << "," << dv23.z << "))" << endstr; - mOutput << "#" << sid+29 << "=DIRECTION('',(" << dv31.x << "," << dv31.y << "," << dv31.z << "))" << endstr; - ind += faceEntryLen; // increase counter + /* n lines and n vectors for the lines for the n edge curves */ + int vectorStart = lineStart + numIndices; + for (int k=0; k < numIndices; ++k) + { + mOutput << "#" << lineStart+k << "=LINE('',#" << pidArray[k] << ",#" << vectorStart+k << ")" << endstr; + } + + int directionStart = vectorStart + numIndices; + for (int k=0; k < numIndices; ++k) + { + mOutput << "#" << vectorStart+k << "=VECTOR('',#" << directionStart+k << ",1.0)" << endstr; + } + + for (int k=0; k < numIndices; ++k) + { + const aiVector3D &dv = dvArray[k]; + mOutput << "#" << directionStart + k << "=DIRECTION('',(" << dv.x << "," << dv.y << "," << dv.z << "))" << endstr; + } + ind += 15 + 5*numIndices; // increase counter } } diff --git a/code/AssetLib/X/XFileExporter.cpp b/code/AssetLib/X/XFileExporter.cpp index da20b935a..b95cb7abf 100644 --- a/code/AssetLib/X/XFileExporter.cpp +++ b/code/AssetLib/X/XFileExporter.cpp @@ -86,7 +86,7 @@ void ExportSceneXFile(const char* pFile,IOSystem* pIOSystem, const aiScene* pSce if (iDoTheExportThing.mOutput.fail()) { throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile)); } - + // we're still here - export successfully completed. Write result to the given IOSYstem std::unique_ptr outfile (pIOSystem->Open(pFile,"wt")); if (outfile == nullptr) { @@ -530,8 +530,8 @@ void XFileExporter::writePath(const aiString &path) while( str.find( "\\\\") != std::string::npos) str.replace( str.find( "\\\\"), 2, "\\"); - while( str.find( "\\") != std::string::npos) - str.replace( str.find( "\\"), 1, "/"); + while (str.find('\\') != std::string::npos) + str.replace(str.find('\\'), 1, "/"); mOutput << str; diff --git a/code/AssetLib/X/XFileExporter.h b/code/AssetLib/X/XFileExporter.h index 32b75b3d1..620d282b6 100644 --- a/code/AssetLib/X/XFileExporter.h +++ b/code/AssetLib/X/XFileExporter.h @@ -94,9 +94,9 @@ protected: void PushTag() { startstr.append( " "); } /// Leaves an element, decreasing the indentation - void PopTag() { - ai_assert( startstr.length() > 1); - startstr.erase( startstr.length() - 2); + void PopTag() { + ai_assert( startstr.length() > 1); + startstr.erase( startstr.length() - 2); } public: diff --git a/code/AssetLib/X/XFileImporter.cpp b/code/AssetLib/X/XFileImporter.cpp index df1aba331..4c8c54551 100644 --- a/code/AssetLib/X/XFileImporter.cpp +++ b/code/AssetLib/X/XFileImporter.cpp @@ -667,9 +667,7 @@ void XFileImporter::ConvertMaterials( aiScene* pScene, std::vector if it comes after // or if (s == "lighting") { @@ -250,7 +250,7 @@ void XGLImporter::ReadWorld(XmlNode &node, TempScope &scope) { } } - aiNode *const nd = ReadObject(node, scope, true); + aiNode *const nd = ReadObject(node, scope); if (!nd) { ThrowException("failure reading "); } @@ -296,16 +296,13 @@ aiLight *XGLImporter::ReadDirectionalLight(XmlNode &node) { } // ------------------------------------------------------------------------------------------------ -aiNode *XGLImporter::ReadObject(XmlNode &node, TempScope &scope, bool skipFirst/*, const char *closetag */) { +aiNode *XGLImporter::ReadObject(XmlNode &node, TempScope &scope) { aiNode *nd = new aiNode; std::vector children; std::vector meshes; try { for (XmlNode &child : node.children()) { - - skipFirst = false; - const std::string &s = ai_stdStrToLower(child.name()); if (s == "mesh") { const size_t prev = scope.meshes_linear.size(); diff --git a/code/AssetLib/XGL/XGLLoader.h b/code/AssetLib/XGL/XGLLoader.h index f7da4e0a7..a2b224ac9 100644 --- a/code/AssetLib/XGL/XGLLoader.h +++ b/code/AssetLib/XGL/XGLLoader.h @@ -185,7 +185,7 @@ private: void ReadWorld(XmlNode &node, TempScope &scope); void ReadLighting(XmlNode &node, TempScope &scope); aiLight *ReadDirectionalLight(XmlNode &node); - aiNode *ReadObject(XmlNode &node, TempScope &scope, bool skipFirst = false/*, const char *closetag = "object"*/); + aiNode *ReadObject(XmlNode &node, TempScope &scope); bool ReadMesh(XmlNode &node, TempScope &scope); void ReadMaterial(XmlNode &node, TempScope &scope); aiVector2D ReadVec2(XmlNode &node); diff --git a/code/AssetLib/glTF/glTFAsset.h b/code/AssetLib/glTF/glTFAsset.h index da49a1737..4cef646d2 100644 --- a/code/AssetLib/glTF/glTFAsset.h +++ b/code/AssetLib/glTF/glTFAsset.h @@ -456,11 +456,10 @@ namespace glTF /// \param [in] pDecodedData - pointer to decoded data array. /// \param [in] pDecodedData_Length - size of encoded region, in bytes. /// \param [in] pID - ID of the region. - SEncodedRegion(const size_t pOffset, const size_t pEncodedData_Length, uint8_t* pDecodedData, const size_t pDecodedData_Length, const std::string pID) - : Offset(pOffset), EncodedData_Length(pEncodedData_Length), DecodedData(pDecodedData), DecodedData_Length(pDecodedData_Length), ID(pID) - {} + SEncodedRegion(const size_t pOffset, const size_t pEncodedData_Length, uint8_t *pDecodedData, const size_t pDecodedData_Length, const std::string &pID) : + Offset(pOffset), EncodedData_Length(pEncodedData_Length), DecodedData(pDecodedData), DecodedData_Length(pDecodedData_Length), ID(pID) {} - /// \fn ~SEncodedRegion() + /// \fn ~SEncodedRegion() /// Destructor. ~SEncodedRegion() { delete [] DecodedData; } }; @@ -1149,8 +1148,7 @@ namespace glTF void ReadExtensionsUsed(Document& doc); - - IOStream* OpenFile(std::string path, const char* mode, bool absolute = false); + IOStream *OpenFile(const std::string &path, const char *mode, bool absolute = false); }; } diff --git a/code/AssetLib/glTF/glTFAsset.inl b/code/AssetLib/glTF/glTFAsset.inl index 6e1e60846..e915a3aee 100644 --- a/code/AssetLib/glTF/glTFAsset.inl +++ b/code/AssetLib/glTF/glTFAsset.inl @@ -1377,7 +1377,7 @@ inline void Asset::ReadExtensionsUsed(Document &doc) { #undef CHECK_EXT } -inline IOStream *Asset::OpenFile(std::string path, const char *mode, bool absolute) { +inline IOStream *Asset::OpenFile(const std::string& path, const char *mode, bool absolute) { #ifdef ASSIMP_API (void)absolute; return mIOSystem->Open(path, mode); diff --git a/code/AssetLib/glTF/glTFCommon.h b/code/AssetLib/glTF/glTFCommon.h index d99ffbe86..6f35e7881 100644 --- a/code/AssetLib/glTF/glTFCommon.h +++ b/code/AssetLib/glTF/glTFCommon.h @@ -195,11 +195,11 @@ inline void CopyValue(const glTFCommon::mat4 &v, aiMatrix4x4 &o) { inline std::string getCurrentAssetDir(const std::string &pFile) { std::string path = pFile; int pos = std::max(int(pFile.rfind('/')), int(pFile.rfind('\\'))); - if (pos != int(std::string::npos)) { - path = pFile.substr(0, pos + 1); + if (pos == int(std::string::npos)) { + return std::string(); } - return path; + return pFile.substr(0, pos + 1); } #if _MSC_VER # pragma warning(pop) diff --git a/code/AssetLib/glTF/glTFExporter.cpp b/code/AssetLib/glTF/glTFExporter.cpp index c2f96973b..acc195bd9 100644 --- a/code/AssetLib/glTF/glTFExporter.cpp +++ b/code/AssetLib/glTF/glTFExporter.cpp @@ -408,8 +408,7 @@ void glTFExporter::ExportMaterials() * Search through node hierarchy and find the node containing the given meshID. * Returns true on success, and false otherwise. */ -bool FindMeshNode(Ref& nodeIn, Ref& meshNode, std::string meshID) -{ +bool FindMeshNode(Ref &nodeIn, Ref &meshNode, const std::string &meshID) { for (unsigned int i = 0; i < nodeIn->meshes.size(); ++i) { if (meshID.compare(nodeIn->meshes[i]->id) == 0) { meshNode = nodeIn; @@ -530,6 +529,7 @@ void ExportSkin(Asset& mAsset, const aiMesh* aimesh, Ref& meshRef, Ref mStringValue; + Nullable mDoubleValue; + Nullable mUint64Value; + Nullable mInt64Value; + Nullable mBoolValue; + + // std::vector handles both Object and Array + Nullable> mValues; + + operator bool() const { + return Size() != 0; + } + + size_t Size() const { + if (mValues.isPresent) { + return mValues.value.size(); + } else if (mStringValue.isPresent || mDoubleValue.isPresent || mUint64Value.isPresent || mInt64Value.isPresent || mBoolValue.isPresent) { + return 1; + } + return 0; + } + + CustomExtension() = default; + + ~CustomExtension() = default; + + CustomExtension(const CustomExtension &other) : + name(other.name), + mStringValue(other.mStringValue), + mDoubleValue(other.mDoubleValue), + mUint64Value(other.mUint64Value), + mInt64Value(other.mInt64Value), + mBoolValue(other.mBoolValue), + mValues(other.mValues) { + // empty + } +}; + //! Base class for all glTF top-level objects struct Object { int index; //!< The index of this object within its property container @@ -363,6 +410,9 @@ struct Object { std::string id; //!< The globally unique ID used to reference this object std::string name; //!< The user-defined name of this object + CustomExtension customExtensions; + CustomExtension extras; + //! Objects marked as special are not exported (used to emulate the binary body buffer) virtual bool IsSpecial() const { return false; } @@ -377,6 +427,9 @@ struct Object { inline Value *FindArray(Value &val, const char *id); inline Value *FindObject(Value &val, const char *id); inline Value *FindExtension(Value &val, const char *extensionId); + + inline void ReadExtensions(Value &val); + inline void ReadExtras(Value &val); }; // @@ -408,7 +461,7 @@ public: /// \param [in] pDecodedData - pointer to decoded data array. /// \param [in] pDecodedData_Length - size of encoded region, in bytes. /// \param [in] pID - ID of the region. - SEncodedRegion(const size_t pOffset, const size_t pEncodedData_Length, uint8_t *pDecodedData, const size_t pDecodedData_Length, const std::string pID) : + SEncodedRegion(const size_t pOffset, const size_t pEncodedData_Length, uint8_t *pDecodedData, const size_t pDecodedData_Length, const std::string &pID) : Offset(pOffset), EncodedData_Length(pEncodedData_Length), DecodedData(pDecodedData), @@ -834,50 +887,6 @@ struct Mesh : public Object { void Read(Value &pJSON_Object, Asset &pAsset_Root); }; -struct CustomExtension : public Object { - // - // A struct containing custom extension data added to a glTF2 file - // Has to contain Object, Array, String, Double, Uint64, and Int64 at a minimum - // String, Double, Uint64, and Int64 are stored in the Nullables - // Object and Array are stored in the std::vector - // - - Nullable mStringValue; - Nullable mDoubleValue; - Nullable mUint64Value; - Nullable mInt64Value; - Nullable mBoolValue; - - // std::vector handles both Object and Array - Nullable> mValues; - - operator bool() const { - return Size() != 0; - } - - size_t Size() const { - if (mValues.isPresent) { - return mValues.value.size(); - } else if (mStringValue.isPresent || mDoubleValue.isPresent || mUint64Value.isPresent || mInt64Value.isPresent || mBoolValue.isPresent) { - return 1; - } - return 0; - } - - CustomExtension() = default; - - CustomExtension(const CustomExtension &other) - : Object(other) - , mStringValue(other.mStringValue) - , mDoubleValue(other.mDoubleValue) - , mUint64Value(other.mUint64Value) - , mInt64Value(other.mInt64Value) - , mBoolValue(other.mBoolValue) - , mValues(other.mValues) - { - } -}; - struct Node : public Object { std::vector> children; std::vector> meshes; @@ -896,8 +905,6 @@ struct Node : public Object { Ref parent; //!< This is not part of the glTF specification. Used as a helper. - CustomExtension extensions; - Node() {} void Read(Value &obj, Asset &r); }; @@ -1188,7 +1195,7 @@ private: void ReadExtensionsUsed(Document &doc); void ReadExtensionsRequired(Document &doc); - IOStream *OpenFile(std::string path, const char *mode, bool absolute = false); + IOStream *OpenFile(const std::string &path, const char *mode, bool absolute = false); }; inline std::string getContextForErrorMessages(const std::string &id, const std::string &name) { diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index b51ac20c2..9f793d7c0 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -4,7 +4,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2021, assimp team - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -304,6 +303,43 @@ inline Value *FindObject(Document &doc, const char *memberId) { inline Value *FindExtension(Value &val, const char *extensionId) { return FindExtensionInContext(val, extensionId, "the document"); } + +inline CustomExtension ReadExtensions(const char *name, Value &obj) { + CustomExtension ret; + ret.name = name; + if (obj.IsObject()) { + ret.mValues.isPresent = true; + for (auto it = obj.MemberBegin(); it != obj.MemberEnd(); ++it) { + auto &val = it->value; + ret.mValues.value.push_back(ReadExtensions(it->name.GetString(), val)); + } + } else if (obj.IsArray()) { + ret.mValues.value.reserve(obj.Size()); + ret.mValues.isPresent = true; + for (unsigned int i = 0; i < obj.Size(); ++i) { + ret.mValues.value.push_back(ReadExtensions(name, obj[i])); + } + } else if (obj.IsNumber()) { + if (obj.IsUint64()) { + ret.mUint64Value.value = obj.GetUint64(); + ret.mUint64Value.isPresent = true; + } else if (obj.IsInt64()) { + ret.mInt64Value.value = obj.GetInt64(); + ret.mInt64Value.isPresent = true; + } else if (obj.IsDouble()) { + ret.mDoubleValue.value = obj.GetDouble(); + ret.mDoubleValue.isPresent = true; + } + } else if (obj.IsString()) { + ReadValue(obj, ret.mStringValue); + ret.mStringValue.isPresent = true; + } else if (obj.IsBool()) { + ret.mBoolValue.value = obj.GetBool(); + ret.mBoolValue.isPresent = true; + } + return ret; +} + } // namespace inline Value *Object::FindString(Value &val, const char *memberId) { @@ -330,6 +366,18 @@ inline Value *Object::FindExtension(Value &val, const char *extensionId) { return FindExtensionInContext(val, extensionId, id.c_str(), name.c_str()); } +inline void Object::ReadExtensions(Value &val) { + if (Value *curExtensions = FindObject(val, "extensions")) { + this->customExtensions = glTF2::ReadExtensions("extensions", *curExtensions); + } +} + +inline void Object::ReadExtras(Value &val) { + if (Value *curExtras = FindObject(val, "extras")) { + this->extras = glTF2::ReadExtensions("extras", *curExtras); + } +} + #ifdef ASSIMP_ENABLE_DRACO template @@ -362,14 +410,14 @@ inline void SetDecodedIndexBuffer_Draco(const draco::Mesh &dracoMesh, Mesh::Prim // Not same size, convert switch (componentBytes) { - case sizeof(uint32_t): - CopyFaceIndex_Draco(*decodedIndexBuffer, dracoMesh); + case sizeof(uint32_t): + CopyFaceIndex_Draco(*decodedIndexBuffer, dracoMesh); break; - case sizeof(uint16_t): - CopyFaceIndex_Draco(*decodedIndexBuffer, dracoMesh); + case sizeof(uint16_t): + CopyFaceIndex_Draco(*decodedIndexBuffer, dracoMesh); break; - case sizeof(uint8_t): - CopyFaceIndex_Draco(*decodedIndexBuffer, dracoMesh); + case sizeof(uint8_t): + CopyFaceIndex_Draco(*decodedIndexBuffer, dracoMesh); break; default: ai_assert(false); @@ -412,23 +460,23 @@ inline void SetDecodedAttributeBuffer_Draco(const draco::Mesh &dracoMesh, uint32 decodedAttribBuffer->Grow(dracoMesh.num_points() * pDracoAttribute->num_components() * componentBytes); switch (accessor.componentType) { - case ComponentType_BYTE: - GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); + case ComponentType_BYTE: + GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); break; case ComponentType_UNSIGNED_BYTE: - GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); + GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); break; case ComponentType_SHORT: GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); break; - case ComponentType_UNSIGNED_SHORT: - GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); + case ComponentType_UNSIGNED_SHORT: + GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); break; - case ComponentType_UNSIGNED_INT: - GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); + case ComponentType_UNSIGNED_INT: + GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); break; - case ComponentType_FLOAT: - GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); + case ComponentType_FLOAT: + GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); break; default: ai_assert(false); @@ -569,6 +617,8 @@ Ref LazyDict::Retrieve(unsigned int i) { inst->oIndex = i; ReadMember(obj, "name", inst->name); inst->Read(obj, mAsset); + inst->ReadExtensions(obj); + inst->ReadExtras(obj); Ref result = Add(inst.release()); mRecursiveReferenceCheck.erase(i); @@ -733,12 +783,13 @@ inline void Buffer::EncodedRegion_Mark(const size_t pOffset, const size_t pEncod } inline void Buffer::EncodedRegion_SetCurrent(const std::string &pID) { - if ((EncodedRegion_Current != nullptr) && (EncodedRegion_Current->ID == pID)) return; + if ((EncodedRegion_Current != nullptr) && (EncodedRegion_Current->ID == pID)) { + return; + } for (SEncodedRegion *reg : EncodedRegion_List) { if (reg->ID == pID) { EncodedRegion_Current = reg; - return; } } @@ -788,10 +839,13 @@ inline bool Buffer::ReplaceData_joint(const size_t pBufferData_Offset, const siz } inline size_t Buffer::AppendData(uint8_t *data, size_t length) { - size_t offset = this->byteLength; + const size_t offset = this->byteLength; + // Force alignment to 4 bits - Grow((length + 3) & ~3); + const size_t paddedLength = (length + 3) & ~3; + Grow(paddedLength); memcpy(mData.get() + offset, data, length); + memset(mData.get() + offset + length, 0, paddedLength - length); return offset; } @@ -820,9 +874,7 @@ inline void Buffer::Grow(size_t amount) { // // struct BufferView // - inline void BufferView::Read(Value &obj, Asset &r) { - if (Value *bufferVal = FindUInt(obj, "buffer")) { buffer = r.buffers.Retrieve(bufferVal->GetUint()); } @@ -842,16 +894,21 @@ inline void BufferView::Read(Value &obj, Asset &r) { } inline uint8_t *BufferView::GetPointer(size_t accOffset) { - if (!buffer) return nullptr; + if (!buffer) { + return nullptr; + } uint8_t *basePtr = buffer->GetPointer(); - if (!basePtr) return nullptr; + if (!basePtr) { + return nullptr; + } size_t offset = accOffset + byteOffset; if (buffer->EncodedRegion_Current != nullptr) { const size_t begin = buffer->EncodedRegion_Current->Offset; const size_t end = begin + buffer->EncodedRegion_Current->DecodedData_Length; - if ((offset >= begin) && (offset < end)) + if ((offset >= begin) && (offset < end)) { return &buffer->EncodedRegion_Current->DecodedData[offset - begin]; + } } return basePtr + offset; @@ -877,18 +934,18 @@ inline void Accessor::Sparse::PatchData(unsigned int elementSize) { while (pIndices != indicesEnd) { size_t offset; switch (indicesType) { - case ComponentType_UNSIGNED_BYTE: - offset = *pIndices; - break; - case ComponentType_UNSIGNED_SHORT: - offset = *reinterpret_cast(pIndices); - break; - case ComponentType_UNSIGNED_INT: - offset = *reinterpret_cast(pIndices); - break; - default: - // have fun with float and negative values from signed types as indices. - throw DeadlyImportError("Unsupported component type in index."); + case ComponentType_UNSIGNED_BYTE: + offset = *pIndices; + break; + case ComponentType_UNSIGNED_SHORT: + offset = *reinterpret_cast(pIndices); + break; + case ComponentType_UNSIGNED_INT: + offset = *reinterpret_cast(pIndices); + break; + default: + // have fun with float and negative values from signed types as indices. + throw DeadlyImportError("Unsupported component type in index."); } offset *= elementSize; @@ -900,7 +957,6 @@ inline void Accessor::Sparse::PatchData(unsigned int elementSize) { } inline void Accessor::Read(Value &obj, Asset &r) { - if (Value *bufferViewVal = FindUInt(obj, "bufferView")) { bufferView = r.bufferViews.Retrieve(bufferViewVal->GetUint()); } @@ -909,9 +965,9 @@ inline void Accessor::Read(Value &obj, Asset &r) { componentType = MemberOrDefault(obj, "componentType", ComponentType_BYTE); { const Value* countValue = FindUInt(obj, "count"); - if (!countValue || countValue->GetUint() < 1) + if (!countValue) { - throw DeadlyImportError("A strictly positive count value is required, when reading ", id.c_str(), name.empty() ? "" : " (" + name + ")"); + throw DeadlyImportError("A count value is required, when reading ", id.c_str(), name.empty() ? "" : " (" + name + ")"); } count = countValue->GetUint(); } @@ -1466,7 +1522,7 @@ inline bool GetAttribTargetVector(Mesh::Primitive &p, const int targetIndex, con inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) { Value *curName = FindMember(pJSON_Object, "name"); - if (nullptr != curName) { + if (nullptr != curName && curName->IsString()) { name = curName->GetString(); } @@ -1612,9 +1668,9 @@ inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) { } } - Value *extras = FindObject(pJSON_Object, "extras"); - if (nullptr != extras) { - if (Value *curTargetNames = FindArray(*extras, "targetNames")) { + Value *curExtras = FindObject(pJSON_Object, "extras"); + if (nullptr != curExtras) { + if (Value *curTargetNames = FindArray(*curExtras, "targetNames")) { this->targetNames.resize(curTargetNames->Size()); for (unsigned int i = 0; i < curTargetNames->Size(); ++i) { Value &targetNameValue = (*curTargetNames)[i]; @@ -1683,42 +1739,6 @@ inline void Light::Read(Value &obj, Asset & /*r*/) { } } -inline CustomExtension ReadExtensions(const char *name, Value &obj) { - CustomExtension ret; - ret.name = name; - if (obj.IsObject()) { - ret.mValues.isPresent = true; - for (auto it = obj.MemberBegin(); it != obj.MemberEnd(); ++it) { - auto &val = it->value; - ret.mValues.value.push_back(ReadExtensions(it->name.GetString(), val)); - } - } else if (obj.IsArray()) { - ret.mValues.value.reserve(obj.Size()); - ret.mValues.isPresent = true; - for (unsigned int i = 0; i < obj.Size(); ++i) { - ret.mValues.value.push_back(ReadExtensions(name, obj[i])); - } - } else if (obj.IsNumber()) { - if (obj.IsUint64()) { - ret.mUint64Value.value = obj.GetUint64(); - ret.mUint64Value.isPresent = true; - } else if (obj.IsInt64()) { - ret.mInt64Value.value = obj.GetInt64(); - ret.mInt64Value.isPresent = true; - } else if (obj.IsDouble()) { - ret.mDoubleValue.value = obj.GetDouble(); - ret.mDoubleValue.isPresent = true; - } - } else if (obj.IsString()) { - ReadValue(obj, ret.mStringValue); - ret.mStringValue.isPresent = true; - } else if (obj.IsBool()) { - ret.mBoolValue.value = obj.GetBool(); - ret.mBoolValue.isPresent = true; - } - return ret; -} - inline void Node::Read(Value &obj, Asset &r) { if (name.empty()) { name = id; @@ -1775,8 +1795,6 @@ inline void Node::Read(Value &obj, Asset &r) { Value *curExtensions = FindObject(obj, "extensions"); if (nullptr != curExtensions) { - this->extensions = ReadExtensions("extensions", *curExtensions); - if (r.extensionsUsed.KHR_lights_punctual) { if (Value *ext = FindObject(*curExtensions, "KHR_lights_punctual")) { Value *curLight = FindUInt(*ext, "light"); @@ -2132,7 +2150,7 @@ inline void Asset::ReadExtensionsUsed(Document &doc) { #undef CHECK_EXT } -inline IOStream *Asset::OpenFile(std::string path, const char *mode, bool /*absolute*/) { +inline IOStream *Asset::OpenFile(const std::string& path, const char *mode, bool /*absolute*/) { #ifdef ASSIMP_API return mIOSystem->Open(path, mode); #else diff --git a/code/AssetLib/glTF2/glTF2AssetWriter.inl b/code/AssetLib/glTF2/glTF2AssetWriter.inl index bf7dbbb2e..115cdf903 100644 --- a/code/AssetLib/glTF2/glTF2AssetWriter.inl +++ b/code/AssetLib/glTF2/glTF2AssetWriter.inl @@ -452,7 +452,7 @@ namespace glTF2 { WriteTex(materialClearcoat, clearcoat.clearcoatTexture, "clearcoatTexture", w.mAl); WriteTex(materialClearcoat, clearcoat.clearcoatRoughnessTexture, "clearcoatRoughnessTexture", w.mAl); WriteTex(materialClearcoat, clearcoat.clearcoatNormalTexture, "clearcoatNormalTexture", w.mAl); - + if (!materialClearcoat.ObjectEmpty()) { exts.AddMember("KHR_materials_clearcoat", materialClearcoat, w.mAl); } @@ -468,7 +468,7 @@ namespace glTF2 { } WriteTex(materialTransmission, transmission.transmissionTexture, "transmissionTexture", w.mAl); - + if (!materialTransmission.ObjectEmpty()) { exts.AddMember("KHR_materials_transmission", materialTransmission, w.mAl); } @@ -613,7 +613,7 @@ namespace glTF2 { if (n.skin) { obj.AddMember("skin", n.skin->index, w.mAl); } - + //gltf2 spec does not support "skeletons" under node if(n.skeletons.size()) { AddRefsVector(obj, "skeletons", n.skeletons, w.mAl); @@ -711,7 +711,7 @@ namespace glTF2 { if (mAsset.scene) { mDoc.AddMember("scene", mAsset.scene->index, mAl); } - + if(mAsset.extras) { mDoc.AddMember("extras", *mAsset.extras, mAl); } @@ -812,7 +812,7 @@ namespace glTF2 { uint32_t binaryChunkLength = 0; if (bodyBuffer->byteLength > 0) { binaryChunkLength = (bodyBuffer->byteLength + 3) & ~3; // Round up to next multiple of 4 - + auto curPaddingLength = binaryChunkLength - bodyBuffer->byteLength; ++GLB_Chunk_count; @@ -881,7 +881,7 @@ namespace glTF2 { if (this->mAsset.extensionsUsed.KHR_materials_sheen) { exts.PushBack(StringRef("KHR_materials_sheen"), mAl); } - + if (this->mAsset.extensionsUsed.KHR_materials_clearcoat) { exts.PushBack(StringRef("KHR_materials_clearcoat"), mAl); } @@ -893,7 +893,7 @@ namespace glTF2 { if (this->mAsset.extensionsUsed.FB_ngon_encoding) { exts.PushBack(StringRef("FB_ngon_encoding"), mAl); } - + if (this->mAsset.extensionsUsed.KHR_texture_basisu) { exts.PushBack(StringRef("KHR_texture_basisu"), mAl); } @@ -901,7 +901,7 @@ namespace glTF2 { if (!exts.Empty()) mDoc.AddMember("extensionsUsed", exts, mAl); - + //basisu extensionRequired Value extsReq; extsReq.SetArray(); diff --git a/code/AssetLib/glTF2/glTF2Exporter.cpp b/code/AssetLib/glTF2/glTF2Exporter.cpp index fe592f342..bbb8a10ff 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.cpp +++ b/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -88,15 +88,13 @@ namespace Assimp { } // end of namespace Assimp glTF2Exporter::glTF2Exporter(const char* filename, IOSystem* pIOSystem, const aiScene* pScene, - const ExportProperties* pProperties, bool isBinary) + const ExportProperties* pProperties, bool isBinary) : mFilename(filename) , mIOSystem(pIOSystem) + , mScene(pScene) , mProperties(pProperties) + , mAsset(new Asset(pIOSystem)) { - mScene = pScene; - - mAsset.reset( new Asset( pIOSystem ) ); - // Always on as our triangulation process is aware of this type of encoding mAsset->extensionsUsed.FB_ngon_encoding = true; @@ -118,14 +116,14 @@ glTF2Exporter::glTF2Exporter(const char* filename, IOSystem* pIOSystem, const ai ExportScene(); ExportAnimations(); - + // export extras if(mProperties->HasPropertyCallback("extras")) { std::function ExportExtras = mProperties->GetPropertyCallback("extras"); mAsset->extras = (rapidjson::Value*)ExportExtras(0); } - + AssetWriter writer(*mAsset); if (isBinary) { @@ -436,11 +434,11 @@ inline void SetSamplerWrap(SamplerWrap& wrap, aiTextureMapMode map) }; } -void glTF2Exporter::GetTexSampler(const aiMaterial* mat, Ref texture, aiTextureType tt, unsigned int slot) +void glTF2Exporter::GetTexSampler(const aiMaterial& mat, Ref texture, aiTextureType tt, unsigned int slot) { aiString aId; std::string id; - if (aiGetMaterialString(mat, AI_MATKEY_GLTF_MAPPINGID(tt, slot), &aId) == AI_SUCCESS) { + if (aiGetMaterialString(&mat, AI_MATKEY_GLTF_MAPPINGID(tt, slot), &aId) == AI_SUCCESS) { id = aId.C_Str(); } @@ -455,49 +453,52 @@ void glTF2Exporter::GetTexSampler(const aiMaterial* mat, Ref texture, a SamplerMagFilter filterMag; SamplerMinFilter filterMin; - if (aiGetMaterialInteger(mat, AI_MATKEY_MAPPINGMODE_U(tt, slot), (int*)&mapU) == AI_SUCCESS) { + if (aiGetMaterialInteger(&mat, AI_MATKEY_MAPPINGMODE_U(tt, slot), (int*)&mapU) == AI_SUCCESS) { SetSamplerWrap(texture->sampler->wrapS, mapU); } - if (aiGetMaterialInteger(mat, AI_MATKEY_MAPPINGMODE_V(tt, slot), (int*)&mapV) == AI_SUCCESS) { + if (aiGetMaterialInteger(&mat, AI_MATKEY_MAPPINGMODE_V(tt, slot), (int*)&mapV) == AI_SUCCESS) { SetSamplerWrap(texture->sampler->wrapT, mapV); } - if (aiGetMaterialInteger(mat, AI_MATKEY_GLTF_MAPPINGFILTER_MAG(tt, slot), (int*)&filterMag) == AI_SUCCESS) { + if (aiGetMaterialInteger(&mat, AI_MATKEY_GLTF_MAPPINGFILTER_MAG(tt, slot), (int*)&filterMag) == AI_SUCCESS) { texture->sampler->magFilter = filterMag; } - if (aiGetMaterialInteger(mat, AI_MATKEY_GLTF_MAPPINGFILTER_MIN(tt, slot), (int*)&filterMin) == AI_SUCCESS) { + if (aiGetMaterialInteger(&mat, AI_MATKEY_GLTF_MAPPINGFILTER_MIN(tt, slot), (int*)&filterMin) == AI_SUCCESS) { texture->sampler->minFilter = filterMin; } aiString name; - if (aiGetMaterialString(mat, AI_MATKEY_GLTF_MAPPINGNAME(tt, slot), &name) == AI_SUCCESS) { + if (aiGetMaterialString(&mat, AI_MATKEY_GLTF_MAPPINGNAME(tt, slot), &name) == AI_SUCCESS) { texture->sampler->name = name.C_Str(); } } } -void glTF2Exporter::GetMatTexProp(const aiMaterial* mat, unsigned int& prop, const char* propName, aiTextureType tt, unsigned int slot) +void glTF2Exporter::GetMatTexProp(const aiMaterial& mat, unsigned int& prop, const char* propName, aiTextureType tt, unsigned int slot) { std::string textureKey = std::string(_AI_MATKEY_TEXTURE_BASE) + "." + propName; - mat->Get(textureKey.c_str(), tt, slot, prop); + mat.Get(textureKey.c_str(), tt, slot, prop); } -void glTF2Exporter::GetMatTexProp(const aiMaterial* mat, float& prop, const char* propName, aiTextureType tt, unsigned int slot) +void glTF2Exporter::GetMatTexProp(const aiMaterial& mat, float& prop, const char* propName, aiTextureType tt, unsigned int slot) { std::string textureKey = std::string(_AI_MATKEY_TEXTURE_BASE) + "." + propName; - mat->Get(textureKey.c_str(), tt, slot, prop); + mat.Get(textureKey.c_str(), tt, slot, prop); } -void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref& texture, aiTextureType tt, unsigned int slot = 0) +void glTF2Exporter::GetMatTex(const aiMaterial& mat, Ref& texture, unsigned int &texCoord, aiTextureType tt, unsigned int slot = 0) { - if (mat->GetTextureCount(tt) > 0) { + if (mat.GetTextureCount(tt) > 0) { aiString tex; - if (mat->Get(AI_MATKEY_TEXTURE(tt, slot), tex) == AI_SUCCESS) { + // Read texcoord (UV map index) + mat.Get(AI_MATKEY_UVWSRC(tt, slot), texCoord); + + if (mat.Get(AI_MATKEY_TEXTURE(tt, slot), tex) == AI_SUCCESS) { std::string path = tex.C_Str(); if (path.size() > 0) { @@ -515,11 +516,10 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref& texture, aiTe std::string imgId = mAsset->FindUniqueID("", "image"); texture->source = mAsset->images.Create(imgId); - if (path[0] == '*') { // embedded - aiTexture* curTex = mScene->mTextures[atoi(&path[1])]; - + const aiTexture* curTex = mScene->GetEmbeddedTexture(path.c_str()); + if (curTex != nullptr) { // embedded texture->source->name = curTex->mFilename.C_Str(); - + //basisu: embedded ktx2, bu if (curTex->achFormatHint[0]) { std::string mimeType = "image/"; @@ -541,7 +541,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref& texture, aiTe mimeType += curTex->achFormatHint; texture->source->mimeType = mimeType; } - + // The asset has its own buffer, see Image::SetData //basisu: "image/ktx2", "image/basis" as is texture->source->SetData(reinterpret_cast(curTex->pcData), curTex->mWidth, *mAsset); @@ -554,7 +554,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref& texture, aiTe useBasisUniversal = true; } } - + //basisu if(useBasisUniversal) { mAsset->extensionsUsed.KHR_texture_basisu = true; @@ -568,45 +568,45 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref& texture, aiTe } } -void glTF2Exporter::GetMatTex(const aiMaterial* mat, TextureInfo& prop, aiTextureType tt, unsigned int slot = 0) +void glTF2Exporter::GetMatTex(const aiMaterial& mat, TextureInfo& prop, aiTextureType tt, unsigned int slot = 0) { Ref& texture = prop.texture; - GetMatTex(mat, texture, tt, slot); + GetMatTex(mat, texture, prop.texCoord, tt, slot); - if (texture) { - GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot); - } + //if (texture) { + // GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot); + //} } -void glTF2Exporter::GetMatTex(const aiMaterial* mat, NormalTextureInfo& prop, aiTextureType tt, unsigned int slot = 0) +void glTF2Exporter::GetMatTex(const aiMaterial& mat, NormalTextureInfo& prop, aiTextureType tt, unsigned int slot = 0) { Ref& texture = prop.texture; - GetMatTex(mat, texture, tt, slot); + GetMatTex(mat, texture, prop.texCoord, tt, slot); if (texture) { - GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot); + //GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot); GetMatTexProp(mat, prop.scale, "scale", tt, slot); } } -void glTF2Exporter::GetMatTex(const aiMaterial* mat, OcclusionTextureInfo& prop, aiTextureType tt, unsigned int slot = 0) +void glTF2Exporter::GetMatTex(const aiMaterial& mat, OcclusionTextureInfo& prop, aiTextureType tt, unsigned int slot = 0) { Ref& texture = prop.texture; - GetMatTex(mat, texture, tt, slot); + GetMatTex(mat, texture, prop.texCoord, tt, slot); if (texture) { - GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot); + //GetMatTexProp(mat, prop.texCoord, "texCoord", tt, slot); GetMatTexProp(mat, prop.strength, "strength", tt, slot); } } -aiReturn glTF2Exporter::GetMatColor(const aiMaterial* mat, vec4& prop, const char* propName, int type, int idx) +aiReturn glTF2Exporter::GetMatColor(const aiMaterial& mat, vec4& prop, const char* propName, int type, int idx) const { aiColor4D col; - aiReturn result = mat->Get(propName, type, idx, col); + aiReturn result = mat.Get(propName, type, idx, col); if (result == AI_SUCCESS) { prop[0] = col.r; prop[1] = col.g; prop[2] = col.b; prop[3] = col.a; @@ -615,37 +615,116 @@ aiReturn glTF2Exporter::GetMatColor(const aiMaterial* mat, vec4& prop, const cha return result; } -aiReturn glTF2Exporter::GetMatColor(const aiMaterial* mat, vec3& prop, const char* propName, int type, int idx) +aiReturn glTF2Exporter::GetMatColor(const aiMaterial& mat, vec3& prop, const char* propName, int type, int idx) const { aiColor3D col; - aiReturn result = mat->Get(propName, type, idx, col); + aiReturn result = mat.Get(propName, type, idx, col); if (result == AI_SUCCESS) { - prop[0] = col.r; prop[1] = col.g; prop[2] = col.b; + prop[0] = col.r; + prop[1] = col.g; + prop[2] = col.b; } return result; } +bool glTF2Exporter::GetMatSpecGloss(const aiMaterial &mat, glTF2::PbrSpecularGlossiness &pbrSG) { + bool result = false; + // If has Glossiness, a Specular Color or Specular Texture, use the KHR_materials_pbrSpecularGlossiness extension + // NOTE: This extension is being considered for deprecation (Dec 2020), may be replaced by KHR_material_specular + + if (mat.Get(AI_MATKEY_GLOSSINESS_FACTOR, pbrSG.glossinessFactor) == AI_SUCCESS) { + result = true; + } else { + // Don't have explicit glossiness, convert from pbr roughness or legacy shininess + float shininess; + if (mat.Get(AI_MATKEY_ROUGHNESS_FACTOR, shininess) == AI_SUCCESS) { + pbrSG.glossinessFactor = 1.0f - shininess; // Extension defines this way + } else if (mat.Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) { + pbrSG.glossinessFactor = shininess / 1000; + } + } + + if (GetMatColor(mat, pbrSG.specularFactor, AI_MATKEY_COLOR_SPECULAR) == AI_SUCCESS) { + result = true; + } + // Add any appropriate textures + GetMatTex(mat, pbrSG.specularGlossinessTexture, aiTextureType_SPECULAR); + + result = result || pbrSG.specularGlossinessTexture.texture; + + if (result) { + // Likely to always have diffuse + GetMatTex(mat, pbrSG.diffuseTexture, aiTextureType_DIFFUSE); + GetMatColor(mat, pbrSG.diffuseFactor, AI_MATKEY_COLOR_DIFFUSE); + } + + return result; +} + +bool glTF2Exporter::GetMatSheen(const aiMaterial &mat, glTF2::MaterialSheen &sheen) { + // Return true if got any valid Sheen properties or textures + if (GetMatColor(mat, sheen.sheenColorFactor, AI_MATKEY_SHEEN_COLOR_FACTOR) != aiReturn_SUCCESS) + return false; + + // Default Sheen color factor {0,0,0} disables Sheen, so do not export + if (sheen.sheenColorFactor == defaultSheenFactor) + return false; + + mat.Get(AI_MATKEY_SHEEN_ROUGHNESS_FACTOR, sheen.sheenRoughnessFactor); + + GetMatTex(mat, sheen.sheenColorTexture, AI_MATKEY_SHEEN_COLOR_TEXTURE); + GetMatTex(mat, sheen.sheenRoughnessTexture, AI_MATKEY_SHEEN_ROUGHNESS_TEXTURE); + + return true; +} + +bool glTF2Exporter::GetMatClearcoat(const aiMaterial &mat, glTF2::MaterialClearcoat &clearcoat) { + if (mat.Get(AI_MATKEY_CLEARCOAT_FACTOR, clearcoat.clearcoatFactor) != aiReturn_SUCCESS) { + return false; + } + + // Clearcoat factor of zero disables Clearcoat, so do not export + if (clearcoat.clearcoatFactor == 0.0f) + return false; + + mat.Get(AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR, clearcoat.clearcoatRoughnessFactor); + + GetMatTex(mat, clearcoat.clearcoatTexture, AI_MATKEY_CLEARCOAT_TEXTURE); + GetMatTex(mat, clearcoat.clearcoatRoughnessTexture, AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE); + GetMatTex(mat, clearcoat.clearcoatNormalTexture, AI_MATKEY_CLEARCOAT_NORMAL_TEXTURE); + + return true; +} + +bool glTF2Exporter::GetMatTransmission(const aiMaterial &mat, glTF2::MaterialTransmission &transmission) { + bool result = mat.Get(AI_MATKEY_TRANSMISSION_FACTOR, transmission.transmissionFactor) == aiReturn_SUCCESS; + GetMatTex(mat, transmission.transmissionTexture, AI_MATKEY_TRANSMISSION_TEXTURE); + return result || transmission.transmissionTexture.texture; +} + void glTF2Exporter::ExportMaterials() { aiString aiName; for (unsigned int i = 0; i < mScene->mNumMaterials; ++i) { - const aiMaterial* mat = mScene->mMaterials[i]; + ai_assert(mScene->mMaterials[i] != nullptr); + + const aiMaterial & mat = *(mScene->mMaterials[i]); std::string id = "material_" + ai_to_string(i); Ref m = mAsset->materials.Create(id); std::string name; - if (mat->Get(AI_MATKEY_NAME, aiName) == AI_SUCCESS) { + if (mat.Get(AI_MATKEY_NAME, aiName) == AI_SUCCESS) { name = aiName.C_Str(); } name = mAsset->FindUniqueID(name, "material"); m->name = name; - GetMatTex(mat, m->pbrMetallicRoughness.baseColorTexture, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE); + GetMatTex(mat, m->pbrMetallicRoughness.baseColorTexture, aiTextureType_BASE_COLOR); if (!m->pbrMetallicRoughness.baseColorTexture.texture) { //if there wasn't a baseColorTexture defined in the source, fallback to any diffuse texture @@ -654,26 +733,26 @@ void glTF2Exporter::ExportMaterials() GetMatTex(mat, m->pbrMetallicRoughness.metallicRoughnessTexture, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); - if (GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR) != AI_SUCCESS) { + if (GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_BASE_COLOR) != AI_SUCCESS) { // if baseColorFactor wasn't defined, then the source is likely not a metallic roughness material. //a fallback to any diffuse color should be used instead GetMatColor(mat, m->pbrMetallicRoughness.baseColorFactor, AI_MATKEY_COLOR_DIFFUSE); } - if (mat->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR, m->pbrMetallicRoughness.metallicFactor) != AI_SUCCESS) { + if (mat.Get(AI_MATKEY_METALLIC_FACTOR, m->pbrMetallicRoughness.metallicFactor) != AI_SUCCESS) { //if metallicFactor wasn't defined, then the source is likely not a PBR file, and the metallicFactor should be 0 m->pbrMetallicRoughness.metallicFactor = 0; } // get roughness if source is gltf2 file - if (mat->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR, m->pbrMetallicRoughness.roughnessFactor) != AI_SUCCESS) { + if (mat.Get(AI_MATKEY_ROUGHNESS_FACTOR, m->pbrMetallicRoughness.roughnessFactor) != AI_SUCCESS) { // otherwise, try to derive and convert from specular + shininess values aiColor4D specularColor; ai_real shininess; if ( - mat->Get(AI_MATKEY_COLOR_SPECULAR, specularColor) == AI_SUCCESS && - mat->Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS + mat.Get(AI_MATKEY_COLOR_SPECULAR, specularColor) == AI_SUCCESS && + mat.Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS ) { // convert specular color to luminance float specularIntensity = specularColor[0] * 0.2125f + specularColor[1] * 0.7154f + specularColor[2] * 0.0721f; @@ -694,103 +773,60 @@ void glTF2Exporter::ExportMaterials() GetMatTex(mat, m->emissiveTexture, aiTextureType_EMISSIVE); GetMatColor(mat, m->emissiveFactor, AI_MATKEY_COLOR_EMISSIVE); - mat->Get(AI_MATKEY_TWOSIDED, m->doubleSided); - mat->Get(AI_MATKEY_GLTF_ALPHACUTOFF, m->alphaCutoff); + mat.Get(AI_MATKEY_TWOSIDED, m->doubleSided); + mat.Get(AI_MATKEY_GLTF_ALPHACUTOFF, m->alphaCutoff); + float opacity; aiString alphaMode; - if (mat->Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode) == AI_SUCCESS) { + if (mat.Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS) { + if (opacity < 1) { + m->alphaMode = "BLEND"; + m->pbrMetallicRoughness.baseColorFactor[3] *= opacity; + } + } + if (mat.Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode) == AI_SUCCESS) { m->alphaMode = alphaMode.C_Str(); - } else { - float opacity; - - if (mat->Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS) { - if (opacity < 1) { - m->alphaMode = "BLEND"; - m->pbrMetallicRoughness.baseColorFactor[3] *= opacity; - } - } } - bool hasPbrSpecularGlossiness = false; - mat->Get(AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS, hasPbrSpecularGlossiness); - - if (hasPbrSpecularGlossiness) { - - if (!mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness) { - mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness = true; - } - + { + // KHR_materials_pbrSpecularGlossiness extension + // NOTE: This extension is being considered for deprecation (Dec 2020) PbrSpecularGlossiness pbrSG; - - GetMatColor(mat, pbrSG.diffuseFactor, AI_MATKEY_COLOR_DIFFUSE); - GetMatColor(mat, pbrSG.specularFactor, AI_MATKEY_COLOR_SPECULAR); - - if (mat->Get(AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR, pbrSG.glossinessFactor) != AI_SUCCESS) { - float shininess; - - if (mat->Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS) { - pbrSG.glossinessFactor = shininess / 1000; - } + if (GetMatSpecGloss(mat, pbrSG)) { + mAsset->extensionsUsed.KHR_materials_pbrSpecularGlossiness = true; + m->pbrSpecularGlossiness = Nullable(pbrSG); } - - GetMatTex(mat, pbrSG.diffuseTexture, aiTextureType_DIFFUSE); - GetMatTex(mat, pbrSG.specularGlossinessTexture, aiTextureType_SPECULAR); - - m->pbrSpecularGlossiness = Nullable(pbrSG); } - bool unlit; - if (mat->Get(AI_MATKEY_GLTF_UNLIT, unlit) == AI_SUCCESS && unlit) { + // glTFv2 is either PBR or Unlit + aiShadingMode shadingMode = aiShadingMode_PBR_BRDF; + mat.Get(AI_MATKEY_SHADING_MODEL, shadingMode); + if (shadingMode == aiShadingMode_Unlit) { mAsset->extensionsUsed.KHR_materials_unlit = true; m->unlit = true; - } + } else { + // These extensions are not compatible with KHR_materials_unlit or KHR_materials_pbrSpecularGlossiness + if (!m->pbrSpecularGlossiness.isPresent) { + // Sheen + MaterialSheen sheen; + if (GetMatSheen(mat, sheen)) { + mAsset->extensionsUsed.KHR_materials_sheen = true; + m->materialSheen = Nullable(sheen); + } - bool hasMaterialSheen = false; - mat->Get(AI_MATKEY_GLTF_MATERIAL_SHEEN, hasMaterialSheen); + MaterialClearcoat clearcoat; + if (GetMatClearcoat(mat, clearcoat)) { + mAsset->extensionsUsed.KHR_materials_clearcoat = true; + m->materialClearcoat = Nullable(clearcoat); + } - if (hasMaterialSheen) { - mAsset->extensionsUsed.KHR_materials_sheen = true; - - MaterialSheen sheen; - - GetMatColor(mat, sheen.sheenColorFactor, AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_FACTOR); - mat->Get(AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_FACTOR, sheen.sheenRoughnessFactor); - GetMatTex(mat, sheen.sheenColorTexture, AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_TEXTURE); - GetMatTex(mat, sheen.sheenRoughnessTexture, AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_TEXTURE); - - m->materialSheen = Nullable(sheen); - } - - bool hasMaterialClearcoat = false; - mat->Get(AI_MATKEY_GLTF_MATERIAL_CLEARCOAT, hasMaterialClearcoat); - - if (hasMaterialClearcoat) { - mAsset->extensionsUsed.KHR_materials_clearcoat= true; - - MaterialClearcoat clearcoat; - - mat->Get(AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_FACTOR, clearcoat.clearcoatFactor); - mat->Get(AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_FACTOR, clearcoat.clearcoatRoughnessFactor); - GetMatTex(mat, clearcoat.clearcoatTexture, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_TEXTURE); - GetMatTex(mat, clearcoat.clearcoatRoughnessTexture, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_TEXTURE); - GetMatTex(mat, clearcoat.clearcoatNormalTexture, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_NORMAL_TEXTURE); - - m->materialClearcoat = Nullable(clearcoat); - } - - bool hasMaterialTransmission = false; - mat->Get(AI_MATKEY_GLTF_MATERIAL_TRANSMISSION, hasMaterialTransmission); - - if (hasMaterialTransmission) { - mAsset->extensionsUsed.KHR_materials_transmission = true; - - MaterialTransmission transmission; - - mat->Get(AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_FACTOR, transmission.transmissionFactor); - GetMatTex(mat, transmission.transmissionTexture, AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_TEXTURE); - - m->materialTransmission = Nullable(transmission); + MaterialTransmission transmission; + if (GetMatTransmission(mat, transmission)) { + mAsset->extensionsUsed.KHR_materials_transmission = true; + m->materialTransmission = Nullable(transmission); + } + } } } } @@ -799,8 +835,7 @@ void glTF2Exporter::ExportMaterials() * Search through node hierarchy and find the node containing the given meshID. * Returns true on success, and false otherwise. */ -bool FindMeshNode(Ref& nodeIn, Ref& meshNode, std::string meshID) -{ +bool FindMeshNode(Ref &nodeIn, Ref &meshNode, const std::string &meshID) { for (unsigned int i = 0; i < nodeIn->meshes.size(); ++i) { if (meshID.compare(nodeIn->meshes[i]->id) == 0) { meshNode = nodeIn; @@ -1301,8 +1336,11 @@ unsigned int glTF2Exporter::ExportNode(const aiNode* n, Ref& parent) void glTF2Exporter::ExportScene() { - const char* sceneName = "defaultScene"; - Ref scene = mAsset->scenes.Create(sceneName); + // Use the name of the scene if specified + const std::string sceneName = (mScene->mName.length > 0) ? mScene->mName.C_Str() : "defaultScene"; + + // Ensure unique + Ref scene = mAsset->scenes.Create(mAsset->FindUniqueID(sceneName, "")); // root node will be the first one exported (idx 0) if (mAsset->nodes.Size() > 0) { diff --git a/code/AssetLib/glTF2/glTF2Exporter.h b/code/AssetLib/glTF2/glTF2Exporter.h index 86497516a..f5238297f 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.h +++ b/code/AssetLib/glTF2/glTF2Exporter.h @@ -72,6 +72,10 @@ namespace glTF2 struct OcclusionTextureInfo; struct Node; struct Texture; + struct PbrSpecularGlossiness; + struct MaterialSheen; + struct MaterialClearcoat; + struct MaterialTransmission; // Vec/matrix types, as raw float arrays typedef float (vec2)[2]; @@ -97,15 +101,19 @@ namespace Assimp protected: void WriteBinaryData(IOStream* outfile, std::size_t sceneLength); - void GetTexSampler(const aiMaterial* mat, glTF2::Ref texture, aiTextureType tt, unsigned int slot); - void GetMatTexProp(const aiMaterial* mat, unsigned int& prop, const char* propName, aiTextureType tt, unsigned int idx); - void GetMatTexProp(const aiMaterial* mat, float& prop, const char* propName, aiTextureType tt, unsigned int idx); - void GetMatTex(const aiMaterial* mat, glTF2::Ref& texture, aiTextureType tt, unsigned int slot); - void GetMatTex(const aiMaterial* mat, glTF2::TextureInfo& prop, aiTextureType tt, unsigned int slot); - void GetMatTex(const aiMaterial* mat, glTF2::NormalTextureInfo& prop, aiTextureType tt, unsigned int slot); - void GetMatTex(const aiMaterial* mat, glTF2::OcclusionTextureInfo& prop, aiTextureType tt, unsigned int slot); - aiReturn GetMatColor(const aiMaterial* mat, glTF2::vec4& prop, const char* propName, int type, int idx); - aiReturn GetMatColor(const aiMaterial* mat, glTF2::vec3& prop, const char* propName, int type, int idx); + void GetTexSampler(const aiMaterial& mat, glTF2::Ref texture, aiTextureType tt, unsigned int slot); + void GetMatTexProp(const aiMaterial& mat, unsigned int& prop, const char* propName, aiTextureType tt, unsigned int idx); + void GetMatTexProp(const aiMaterial& mat, float& prop, const char* propName, aiTextureType tt, unsigned int idx); + void GetMatTex(const aiMaterial& mat, glTF2::Ref& texture, unsigned int &texCoord, aiTextureType tt, unsigned int slot); + void GetMatTex(const aiMaterial& mat, glTF2::TextureInfo& prop, aiTextureType tt, unsigned int slot); + void GetMatTex(const aiMaterial& mat, glTF2::NormalTextureInfo& prop, aiTextureType tt, unsigned int slot); + void GetMatTex(const aiMaterial& mat, glTF2::OcclusionTextureInfo& prop, aiTextureType tt, unsigned int slot); + aiReturn GetMatColor(const aiMaterial& mat, glTF2::vec4& prop, const char* propName, int type, int idx) const; + aiReturn GetMatColor(const aiMaterial& mat, glTF2::vec3& prop, const char* propName, int type, int idx) const; + bool GetMatSpecGloss(const aiMaterial& mat, glTF2::PbrSpecularGlossiness& pbrSG); + bool GetMatSheen(const aiMaterial& mat, glTF2::MaterialSheen& sheen); + bool GetMatClearcoat(const aiMaterial& mat, glTF2::MaterialClearcoat& clearcoat); + bool GetMatTransmission(const aiMaterial& mat, glTF2::MaterialTransmission& transmission); void ExportMetadata(); void ExportMaterials(); void ExportMeshes(); diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index c62989c3b..ada7aa046 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -165,7 +165,8 @@ inline void SetMaterialTextureProperty(std::vector &embeddedTexIdxs, Asset } mat->AddProperty(&uri, AI_MATKEY_TEXTURE(texType, texSlot)); - mat->AddProperty(&prop.texCoord, 1, AI_MATKEY_GLTF_TEXTURE_TEXCOORD(texType, texSlot)); + const int uvIndex = static_cast(prop.texCoord); + mat->AddProperty(&uvIndex, 1, AI_MATKEY_UVWSRC(texType, texSlot)); if (prop.textureTransformSupported) { aiUVTransform transform; @@ -208,6 +209,11 @@ inline void SetMaterialTextureProperty(std::vector &embeddedTexIdxs, Asset if (sampler->minFilter != SamplerMinFilter::UNSET) { mat->AddProperty(&sampler->minFilter, 1, AI_MATKEY_GLTF_MAPPINGFILTER_MIN(texType, texSlot)); } + } else { + // Use glTFv2 default sampler + const aiTextureMapMode default_wrap = aiTextureMapMode_Wrap; + mat->AddProperty(&default_wrap, 1, AI_MATKEY_MAPPINGMODE_U(texType, texSlot)); + mat->AddProperty(&default_wrap, 1, AI_MATKEY_MAPPINGMODE_V(texType, texSlot)); } } } @@ -238,16 +244,18 @@ static aiMaterial *ImportMaterial(std::vector &embeddedTexIdxs, Asset &r, M aimat->AddProperty(&str, AI_MATKEY_NAME); } + // Set Assimp DIFFUSE and BASE COLOR to the pbrMetallicRoughness base color and texture for backwards compatibility + // Technically should not load any pbrMetallicRoughness if extensionsRequired contains KHR_materials_pbrSpecularGlossiness SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_COLOR_DIFFUSE); - SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR); + SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_BASE_COLOR); SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, aiTextureType_DIFFUSE); - SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE); + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, aiTextureType_BASE_COLOR); SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.metallicRoughnessTexture, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); - aimat->AddProperty(&mat.pbrMetallicRoughness.metallicFactor, 1, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR); - aimat->AddProperty(&mat.pbrMetallicRoughness.roughnessFactor, 1, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR); + aimat->AddProperty(&mat.pbrMetallicRoughness.metallicFactor, 1, AI_MATKEY_METALLIC_FACTOR); + aimat->AddProperty(&mat.pbrMetallicRoughness.roughnessFactor, 1, AI_MATKEY_ROUGHNESS_FACTOR); float roughnessAsShininess = 1 - mat.pbrMetallicRoughness.roughnessFactor; roughnessAsShininess *= roughnessAsShininess * 1000; @@ -259,6 +267,7 @@ static aiMaterial *ImportMaterial(std::vector &embeddedTexIdxs, Asset &r, M SetMaterialColorProperty(r, mat.emissiveFactor, aimat, AI_MATKEY_COLOR_EMISSIVE); aimat->AddProperty(&mat.doubleSided, 1, AI_MATKEY_TWOSIDED); + aimat->AddProperty(&mat.pbrMetallicRoughness.baseColorFactor[3], 1, AI_MATKEY_OPACITY); aiString alphaMode(mat.alphaMode); aimat->AddProperty(&alphaMode, AI_MATKEY_GLTF_ALPHAMODE); @@ -268,52 +277,58 @@ static aiMaterial *ImportMaterial(std::vector &embeddedTexIdxs, Asset &r, M if (mat.pbrSpecularGlossiness.isPresent) { PbrSpecularGlossiness &pbrSG = mat.pbrSpecularGlossiness.value; - aimat->AddProperty(&mat.pbrSpecularGlossiness.isPresent, 1, AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS); SetMaterialColorProperty(r, pbrSG.diffuseFactor, aimat, AI_MATKEY_COLOR_DIFFUSE); SetMaterialColorProperty(r, pbrSG.specularFactor, aimat, AI_MATKEY_COLOR_SPECULAR); float glossinessAsShininess = pbrSG.glossinessFactor * 1000.0f; aimat->AddProperty(&glossinessAsShininess, 1, AI_MATKEY_SHININESS); - aimat->AddProperty(&pbrSG.glossinessFactor, 1, AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR); + aimat->AddProperty(&pbrSG.glossinessFactor, 1, AI_MATKEY_GLOSSINESS_FACTOR); SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.diffuseTexture, aimat, aiTextureType_DIFFUSE); SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.specularGlossinessTexture, aimat, aiTextureType_SPECULAR); } + + // glTFv2 is either PBR or Unlit + aiShadingMode shadingMode = aiShadingMode_PBR_BRDF; if (mat.unlit) { - aimat->AddProperty(&mat.unlit, 1, AI_MATKEY_GLTF_UNLIT); + shadingMode = aiShadingMode_Unlit; } - //KHR_materials_sheen + aimat->AddProperty(&shadingMode, 1, AI_MATKEY_SHADING_MODEL); + + + // KHR_materials_sheen if (mat.materialSheen.isPresent) { MaterialSheen &sheen = mat.materialSheen.value; - - aimat->AddProperty(&mat.materialSheen.isPresent, 1, AI_MATKEY_GLTF_MATERIAL_SHEEN); - SetMaterialColorProperty(r, sheen.sheenColorFactor, aimat, AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_FACTOR); - aimat->AddProperty(&sheen.sheenRoughnessFactor, 1, AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_FACTOR); - SetMaterialTextureProperty(embeddedTexIdxs, r, sheen.sheenColorTexture, aimat, AI_MATKEY_GLTF_MATERIAL_SHEEN_COLOR_TEXTURE); - SetMaterialTextureProperty(embeddedTexIdxs, r, sheen.sheenRoughnessTexture, aimat, AI_MATKEY_GLTF_MATERIAL_SHEEN_ROUGHNESS_TEXTURE); + // Default value {0,0,0} disables Sheen + if (sheen.sheenColorFactor != defaultSheenFactor) { + SetMaterialColorProperty(r, sheen.sheenColorFactor, aimat, AI_MATKEY_SHEEN_COLOR_FACTOR); + aimat->AddProperty(&sheen.sheenRoughnessFactor, 1, AI_MATKEY_SHEEN_ROUGHNESS_FACTOR); + SetMaterialTextureProperty(embeddedTexIdxs, r, sheen.sheenColorTexture, aimat, AI_MATKEY_SHEEN_COLOR_TEXTURE); + SetMaterialTextureProperty(embeddedTexIdxs, r, sheen.sheenRoughnessTexture, aimat, AI_MATKEY_SHEEN_ROUGHNESS_TEXTURE); + } } - //KHR_materials_clearcoat + // KHR_materials_clearcoat if (mat.materialClearcoat.isPresent) { MaterialClearcoat &clearcoat = mat.materialClearcoat.value; - - aimat->AddProperty(&mat.materialClearcoat.isPresent, 1, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT); - aimat->AddProperty(&clearcoat.clearcoatFactor, 1, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_FACTOR); - aimat->AddProperty(&clearcoat.clearcoatRoughnessFactor, 1, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_FACTOR); - SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatTexture, aimat, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_TEXTURE); - SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatRoughnessTexture, aimat, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_ROUGHNESS_TEXTURE); - SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatNormalTexture, aimat, AI_MATKEY_GLTF_MATERIAL_CLEARCOAT_NORMAL_TEXTURE); + // Default value 0.0 disables clearcoat + if (clearcoat.clearcoatFactor != 0.0f) { + aimat->AddProperty(&clearcoat.clearcoatFactor, 1, AI_MATKEY_CLEARCOAT_FACTOR); + aimat->AddProperty(&clearcoat.clearcoatRoughnessFactor, 1, AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR); + SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatTexture, aimat, AI_MATKEY_CLEARCOAT_TEXTURE); + SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatRoughnessTexture, aimat, AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE); + SetMaterialTextureProperty(embeddedTexIdxs, r, clearcoat.clearcoatNormalTexture, aimat, AI_MATKEY_CLEARCOAT_NORMAL_TEXTURE); + } } - //KHR_materials_transmission + // KHR_materials_transmission if (mat.materialTransmission.isPresent) { MaterialTransmission &transmission = mat.materialTransmission.value; - aimat->AddProperty(&mat.materialTransmission.isPresent, 1, AI_MATKEY_GLTF_MATERIAL_TRANSMISSION); - aimat->AddProperty(&transmission.transmissionFactor, 1, AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_FACTOR); - SetMaterialTextureProperty(embeddedTexIdxs, r, transmission.transmissionTexture, aimat, AI_MATKEY_GLTF_MATERIAL_TRANSMISSION_TEXTURE); + aimat->AddProperty(&transmission.transmissionFactor, 1, AI_MATKEY_TRANSMISSION_FACTOR); + SetMaterialTextureProperty(embeddedTexIdxs, r, transmission.transmissionTexture, aimat, AI_MATKEY_TRANSMISSION_TEXTURE); } return aimat; @@ -489,7 +504,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) { "\" does not match the vertex count"); continue; } - + auto componentType = attr.color[c]->componentType; if (componentType == glTF2::ComponentType_FLOAT) { attr.color[c]->ExtractData(aim->mColors[c]); @@ -977,13 +992,21 @@ void ParseExtensions(aiMetadata *metadata, const CustomExtension &extension) { metadata->Add(extension.name.c_str(), extension.mBoolValue.value); } else if (extension.mValues.isPresent) { aiMetadata val; - for (size_t i = 0; i < extension.mValues.value.size(); ++i) { - ParseExtensions(&val, extension.mValues.value[i]); + for (auto const & subExtension : extension.mValues.value) { + ParseExtensions(&val, subExtension); } metadata->Add(extension.name.c_str(), val); } } +void ParseExtras(aiMetadata *metadata, const CustomExtension &extension) { + if (extension.mValues.isPresent) { + for (auto const & subExtension : extension.mValues.value) { + ParseExtensions(metadata, subExtension); + } + } +} + aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector &meshOffsets, glTF2::Ref &ptr) { Node &node = *ptr; @@ -1002,9 +1025,14 @@ aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector & } } - if (node.extensions) { + if (node.customExtensions || node.extras) { ainode->mMetaData = new aiMetadata; - ParseExtensions(ainode->mMetaData, node.extensions); + if (node.customExtensions) { + ParseExtensions(ainode->mMetaData, node.customExtensions); + } + if (node.extras) { + ParseExtras(ainode->mMetaData, node.extras); + } } GetNodeTransform(ainode->mTransformation, node); @@ -1308,6 +1336,23 @@ std::unordered_map GatherSamplers(Animation &an continue; } + auto& animsampler = anim.samplers[channel.sampler]; + + if (!animsampler.input) { + ASSIMP_LOG_WARN("Animation ", anim.name, ": Missing sampler input. Skipping."); + continue; + } + + if (!animsampler.output) { + ASSIMP_LOG_WARN("Animation ", anim.name, ": Missing sampler output. Skipping."); + continue; + } + + if (animsampler.input->count > animsampler.output->count) { + ASSIMP_LOG_WARN("Animation ", anim.name, ": Number of keyframes in sampler input ", animsampler.input->count, " exceeds number of keyframes in sampler output ", animsampler.output->count); + continue; + } + const unsigned int node_index = channel.target.node.GetIndex(); AnimationSamplers &sampler = samplers[node_index]; @@ -1442,10 +1487,11 @@ void glTF2Importer::ImportEmbeddedTextures(glTF2::Asset &r) { } } - if (numEmbeddedTexs == 0) + if (numEmbeddedTexs == 0) { return; + } - ASSIMP_LOG_DEBUG("Importing ", numEmbeddedTexs, " embedded textures"); + ASSIMP_LOG_DEBUG("Importing ", numEmbeddedTexs, " embedded textures"); mScene->mTextures = new aiTexture *[numEmbeddedTexs]; std::fill(mScene->mTextures, mScene->mTextures + numEmbeddedTexs, nullptr); @@ -1498,7 +1544,8 @@ void glTF2Importer::ImportCommonMetadata(glTF2::Asset& a) { const bool hasVersion = !a.asset.version.empty(); const bool hasGenerator = !a.asset.generator.empty(); const bool hasCopyright = !a.asset.copyright.empty(); - if (hasVersion || hasGenerator || hasCopyright) { + const bool hasSceneMetadata = a.scene->customExtensions; + if (hasVersion || hasGenerator || hasCopyright || hasSceneMetadata) { mScene->mMetaData = new aiMetadata; if (hasVersion) { mScene->mMetaData->Add(AI_METADATA_SOURCE_FORMAT_VERSION, aiString(a.asset.version)); @@ -1509,6 +1556,9 @@ void glTF2Importer::ImportCommonMetadata(glTF2::Asset& a) { if (hasCopyright) { mScene->mMetaData->Add(AI_METADATA_SOURCE_COPYRIGHT, aiString(a.asset.copyright)); } + if (hasSceneMetadata) { + ParseExtensions(mScene->mMetaData, a.scene->customExtensions); + } } } diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index be1619e81..933b5488c 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -822,7 +822,10 @@ ADD_ASSIMP_IMPORTER( GLTF AssetLib/glTF2/glTF2Importer.h ) -ADD_ASSIMP_IMPORTER( 3MF +ADD_ASSIMP_IMPORTER(3MF + AssetLib/3MF/3MFTypes.h + AssetLib/3MF/XmlSerializer.h + AssetLib/3MF/XmlSerializer.cpp AssetLib/3MF/D3MFImporter.h AssetLib/3MF/D3MFImporter.cpp AssetLib/3MF/D3MFOpcPackage.h @@ -875,6 +878,7 @@ ELSE() ../contrib/pugixml/src/pugiconfig.hpp ../contrib/pugixml/src/pugixml.hpp ) + INCLUDE_DIRECTORIES("../contrib/pugixml/src") SOURCE_GROUP( Contrib\\Pugixml FILES ${Pugixml_SRCS}) ENDIF() @@ -1035,16 +1039,26 @@ IF(ASSIMP_HUNTER_ENABLED) hunter_add_package(RapidJSON) find_package(RapidJSON CONFIG REQUIRED) ELSE() - INCLUDE_DIRECTORIES( "../contrib/rapidjson/include" ) - INCLUDE_DIRECTORIES( "../contrib" ) - INCLUDE_DIRECTORIES( "../contrib/pugixml/src" ) - ADD_DEFINITIONS( -DRAPIDJSON_HAS_STDSTRING=1 ) + INCLUDE_DIRECTORIES("../contrib/rapidjson/include") + ADD_DEFINITIONS( -DRAPIDJSON_HAS_STDSTRING=1) option( ASSIMP_RAPIDJSON_NO_MEMBER_ITERATOR "Suppress rapidjson warning on MSVC (NOTE: breaks android build)" ON ) if(ASSIMP_RAPIDJSON_NO_MEMBER_ITERATOR) ADD_DEFINITIONS( -DRAPIDJSON_NOMEMBERITERATORCLASS ) endif() ENDIF() +# stb +IF(ASSIMP_HUNTER_ENABLED) + hunter_add_package(stb) + find_package(stb CONFIG REQUIRED) +ELSE() + SET( stb_SRCS + ../contrib/stb/stb_image.h + ) + INCLUDE_DIRECTORIES("../contrib") + SOURCE_GROUP( Contrib\\stb FILES ${stb_SRCS}) +ENDIF() + # VC2010 fixes if(MSVC10) option( VC10_STDINT_FIX "Fix for VC10 Compiler regarding pstdint.h redefinition errors" OFF ) @@ -1103,6 +1117,7 @@ SET( assimp_src ${open3dgc_SRCS} ${ziplib_SRCS} ${Pugixml_SRCS} + ${stb_SRCS} # Necessary to show the headers in the project when using the VC++ generator: ${PUBLIC_HEADERS} @@ -1160,8 +1175,9 @@ IF(ASSIMP_HUNTER_ENABLED) utf8cpp zip::zip pugixml + stb::stb ) - + if (ASSIMP_BUILD_DRACO) target_link_libraries(assimp PUBLIC ${draco_LIBRARIES}) endif() diff --git a/code/Common/Assimp.cpp b/code/Common/Assimp.cpp index 4e2c7117c..3a0ec7d60 100644 --- a/code/Common/Assimp.cpp +++ b/code/Common/Assimp.cpp @@ -72,12 +72,25 @@ namespace Assimp { // underlying structure for aiPropertyStore typedef BatchLoader::PropertyMap PropertyMap; +#if defined(__has_warning) +#if __has_warning("-Wordered-compare-function-pointers") +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wordered-compare-function-pointers" +#endif +#endif + /** Stores the LogStream objects for all active C log streams */ struct mpred { bool operator()(const aiLogStream &s0, const aiLogStream &s1) const { return s0.callback < s1.callback && s0.user < s1.user; } }; + +#if defined(__has_warning) +#if __has_warning("-Wordered-compare-function-pointers") +#pragma GCC diagnostic pop +#endif +#endif typedef std::map LogStreamMap; /** Stores the LogStream objects allocated by #aiGetPredefinedLogStream */ @@ -1251,3 +1264,36 @@ ASSIMP_API void aiQuaternionInterpolate( ai_assert(nullptr != end); aiQuaternion::Interpolate(*dst, *start, *end, factor); } + + +// stb_image is a lightweight image loader. It is shared by: +// - M3D import +// - PBRT export +// Since it's a header-only library, its implementation must be instantiated in some cpp file. +// Don't scatter this task over multiple importers/exporters. Maintain it in a central place (here!). + +#define ASSIMP_HAS_PBRT_EXPORT (!ASSIMP_BUILD_NO_EXPORT && !ASSIMP_BUILD_NO_PBRT_EXPORTER) +#define ASSIMP_HAS_M3D ((!ASSIMP_BUILD_NO_EXPORT && !ASSIMP_BUILD_NO_M3D_EXPORTER) || !ASSIMP_BUILD_NO_M3D_IMPORTER) + +#if ASSIMP_HAS_PBRT_EXPORT +# define ASSIMP_NEEDS_STB_IMAGE 1 +#elif ASSIMP_HAS_M3D +# define ASSIMP_NEEDS_STB_IMAGE 1 +# define STBI_ONLY_PNG +#endif + +#if ASSIMP_NEEDS_STB_IMAGE + +# if _MSC_VER // "unreferenced function has been removed" (SSE2 detection routine in x64 builds) +# pragma warning(push) +# pragma warning(disable: 4505) +# endif + +# define STB_IMAGE_IMPLEMENTATION +# include "stb/stb_image.h" + +# if _MSC_VER +# pragma warning(pop) +# endif + +#endif diff --git a/code/Common/DefaultIOStream.cpp b/code/Common/DefaultIOStream.cpp index ba9b9d625..557fbc78f 100644 --- a/code/Common/DefaultIOStream.cpp +++ b/code/Common/DefaultIOStream.cpp @@ -62,7 +62,7 @@ inline int select_fseek(FILE *file, int64_t offset, int origin) { } - + #if defined _WIN32 && (!defined __GNUC__ || __MSVCRT_VERSION__ >= 0x0601) template <> inline size_t select_ftell<8>(FILE *file) { @@ -75,7 +75,7 @@ inline int select_fseek<8>(FILE *file, int64_t offset, int origin) { } #endif // #if defined _WIN32 && (!defined __GNUC__ || __MSVCRT_VERSION__ >= 0x0601) - + } // namespace // ---------------------------------------------------------------------------------- @@ -95,7 +95,7 @@ size_t DefaultIOStream::Read(void *pvBuffer, } ai_assert(nullptr != pvBuffer); ai_assert(0 != pSize); - + return (mFile ? ::fread(pvBuffer, pSize, pCount, mFile) : 0); } diff --git a/code/Common/DefaultIOSystem.cpp b/code/Common/DefaultIOSystem.cpp index 98d51a17d..de93909de 100644 --- a/code/Common/DefaultIOSystem.cpp +++ b/code/Common/DefaultIOSystem.cpp @@ -71,7 +71,7 @@ static std::wstring Utf8ToWide(const char *in) { // size includes terminating null; std::wstring adds null automatically std::wstring out(static_cast(size) - 1, L'\0'); MultiByteToWideChar(CP_UTF8, 0, in, -1, &out[0], size); - + return out; } @@ -85,7 +85,7 @@ static std::string WideToUtf8(const wchar_t *in) { // size includes terminating null; std::string adds null automatically std::string out(static_cast(size) - 1, '\0'); WideCharToMultiByte(CP_UTF8, 0, in, -1, &out[0], size, nullptr, nullptr); - + return out; } #endif @@ -121,7 +121,7 @@ IOStream *DefaultIOSystem::Open(const char *strFile, const char *strMode) { if (name.empty()) { return nullptr; } - + file = ::_wfopen(name.c_str(), Utf8ToWide(strMode).c_str()); #else file = ::fopen(strFile, strMode); @@ -173,7 +173,7 @@ inline static std::string MakeAbsolutePath(const char *in) { free(ret); } #endif - if (!ret) { + else { // preserve the input path, maybe someone else is able to fix // the path before it is accessed (e.g. our file system filter) ASSIMP_LOG_WARN("Invalid path: ", std::string(in)); diff --git a/code/Common/Exporter.cpp b/code/Common/Exporter.cpp index ebcc955df..512bbf447 100644 --- a/code/Common/Exporter.cpp +++ b/code/Common/Exporter.cpp @@ -83,7 +83,7 @@ namespace Assimp { void GetPostProcessingStepInstanceList(std::vector< BaseProcess* >& out); // ------------------------------------------------------------------------------------------------ -// Exporter worker function prototypes. Do not use const, because some exporter need to convert +// Exporter worker function prototypes. Do not use const, because some exporter need to convert // the scene temporary #ifndef ASSIMP_BUILD_NO_COLLADA_EXPORTER void ExportSceneCollada(const char*,IOSystem*, const aiScene*, const ExportProperties*); @@ -343,7 +343,7 @@ const aiExportDataBlob* Exporter::ExportToBlob( const aiScene* pScene, const cha delete pimpl->blob; pimpl->blob = nullptr; } - + auto baseName = pProperties ? pProperties->GetPropertyString(AI_CONFIG_EXPORT_BLOB_NAME, AI_BLOBIO_MAGIC) : AI_BLOBIO_MAGIC; std::shared_ptr old = pimpl->mIOSystem; diff --git a/code/Common/FileSystemFilter.h b/code/Common/FileSystemFilter.h index 6585f9df6..6782dd9e5 100644 --- a/code/Common/FileSystemFilter.h +++ b/code/Common/FileSystemFilter.h @@ -101,7 +101,7 @@ public: /** Tests for the existence of a file at the given path. */ bool Exists( const char* pFile) const { ai_assert( nullptr != mWrapped ); - + std::string tmp = pFile; // Currently this IOSystem is also used to open THE ONE FILE. @@ -126,7 +126,7 @@ public: if ( nullptr == pFile || nullptr == pMode ) { return nullptr; } - + ai_assert( nullptr != pFile ); ai_assert( nullptr != pMode ); diff --git a/code/Common/Importer.cpp b/code/Common/Importer.cpp index a2ad041fb..d0ed3c788 100644 --- a/code/Common/Importer.cpp +++ b/code/Common/Importer.cpp @@ -201,7 +201,7 @@ Importer::~Importer() { // Register a custom post-processing step aiReturn Importer::RegisterPPStep(BaseProcess* pImp) { ai_assert( nullptr != pImp ); - + ASSIMP_BEGIN_EXCEPTION_REGION(); pimpl->mPostProcessingSteps.push_back(pImp); @@ -215,7 +215,7 @@ aiReturn Importer::RegisterPPStep(BaseProcess* pImp) { // Register a custom loader plugin aiReturn Importer::RegisterLoader(BaseImporter* pImp) { ai_assert(nullptr != pImp); - + ASSIMP_BEGIN_EXCEPTION_REGION(); // -------------------------------------------------------------------- @@ -242,7 +242,7 @@ aiReturn Importer::RegisterLoader(BaseImporter* pImp) { pimpl->mImporter.push_back(pImp); ASSIMP_LOG_INFO("Registering custom importer for these file extensions: ", baked); ASSIMP_END_EXCEPTION_REGION(aiReturn); - + return AI_SUCCESS; } @@ -296,7 +296,7 @@ aiReturn Importer::UnregisterPPStep(BaseProcess* pImp) { // Supplies a custom IO handler to the importer to open and access files. void Importer::SetIOHandler( IOSystem* pIOHandler) { ai_assert(nullptr != pimpl); - + ASSIMP_BEGIN_EXCEPTION_REGION(); // If the new handler is zero, allocate a default IO implementation. if (!pIOHandler) { @@ -315,7 +315,7 @@ void Importer::SetIOHandler( IOSystem* pIOHandler) { // Get the currently set IO handler IOSystem* Importer::GetIOHandler() const { ai_assert(nullptr != pimpl); - + return pimpl->mIOHandler; } @@ -323,7 +323,7 @@ IOSystem* Importer::GetIOHandler() const { // Check whether a custom IO handler is currently set bool Importer::IsDefaultIOHandler() const { ai_assert(nullptr != pimpl); - + return pimpl->mIsDefaultHandler; } @@ -331,9 +331,9 @@ bool Importer::IsDefaultIOHandler() const { // Supplies a custom progress handler to get regular callbacks during importing void Importer::SetProgressHandler ( ProgressHandler* pHandler ) { ai_assert(nullptr != pimpl); - + ASSIMP_BEGIN_EXCEPTION_REGION(); - + // If the new handler is zero, allocate a default implementation. if (!pHandler) { // Release pointer in the possession of the caller @@ -351,7 +351,7 @@ void Importer::SetProgressHandler ( ProgressHandler* pHandler ) { // Get the currently set progress handler ProgressHandler* Importer::GetProgressHandler() const { ai_assert(nullptr != pimpl); - + return pimpl->mProgressHandler; } @@ -359,7 +359,7 @@ ProgressHandler* Importer::GetProgressHandler() const { // Check whether a custom progress handler is currently set bool Importer::IsDefaultProgressHandler() const { ai_assert(nullptr != pimpl); - + return pimpl->mIsDefaultProgressHandler; } @@ -381,7 +381,7 @@ bool _ValidateFlags(unsigned int pFlags) { // Free the current scene void Importer::FreeScene( ) { ai_assert(nullptr != pimpl); - + ASSIMP_BEGIN_EXCEPTION_REGION(); delete pimpl->mScene; @@ -396,14 +396,14 @@ void Importer::FreeScene( ) { // Get the current error string, if any const char* Importer::GetErrorString() const { ai_assert(nullptr != pimpl); - + // Must remain valid as long as ReadFile() or FreeFile() are not called return pimpl->mErrorString.c_str(); } const std::exception_ptr& Importer::GetException() const { ai_assert(nullptr != pimpl); - + // Must remain valid as long as ReadFile() or FreeFile() are not called return pimpl->mException; } @@ -412,7 +412,7 @@ const std::exception_ptr& Importer::GetException() const { // Enable extra-verbose mode void Importer::SetExtraVerbose(bool bDo) { ai_assert(nullptr != pimpl); - + pimpl->bExtraVerbose = bDo; } @@ -420,7 +420,7 @@ void Importer::SetExtraVerbose(bool bDo) { // Get the current scene const aiScene* Importer::GetScene() const { ai_assert(nullptr != pimpl); - + return pimpl->mScene; } @@ -428,7 +428,7 @@ const aiScene* Importer::GetScene() const { // Orphan the current scene and return it. aiScene* Importer::GetOrphanedScene() { ai_assert(nullptr != pimpl); - + aiScene* s = pimpl->mScene; ASSIMP_BEGIN_EXCEPTION_REGION(); @@ -437,7 +437,7 @@ aiScene* Importer::GetOrphanedScene() { pimpl->mErrorString = std::string(); pimpl->mException = std::exception_ptr(); ASSIMP_END_EXCEPTION_REGION(aiScene*); - + return s; } @@ -487,7 +487,7 @@ const aiScene* Importer::ReadFileFromMemory( const void* pBuffer, unsigned int pFlags, const char* pHint /*= ""*/) { ai_assert(nullptr != pimpl); - + ASSIMP_BEGIN_EXCEPTION_REGION(); if (!pHint) { pHint = ""; @@ -518,7 +518,7 @@ const aiScene* Importer::ReadFileFromMemory( const void* pBuffer, // ------------------------------------------------------------------------------------------------ void WriteLogOpening(const std::string& file) { - + ASSIMP_LOG_INFO("Load ", file); // print a full version dump. This is nice because we don't @@ -580,7 +580,7 @@ void WriteLogOpening(const std::string& file) { // Reads the given file and returns its contents if successful. const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags) { ai_assert(nullptr != pimpl); - + ASSIMP_BEGIN_EXCEPTION_REGION(); const std::string pFile(_pFile); @@ -745,7 +745,7 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags) { // either successful or failure - the pointer expresses it anyways ASSIMP_END_EXCEPTION_REGION_WITH_ERROR_STRING(const aiScene*, pimpl->mErrorString, pimpl->mException); - + return pimpl->mScene; } @@ -754,7 +754,7 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags) { // Apply post-processing to the currently bound scene const aiScene* Importer::ApplyPostProcessing(unsigned int pFlags) { ai_assert(nullptr != pimpl); - + ASSIMP_BEGIN_EXCEPTION_REGION(); // Return immediately if no scene is active if (!pimpl->mScene) { @@ -832,7 +832,7 @@ const aiScene* Importer::ApplyPostProcessing(unsigned int pFlags) { } #endif // ! DEBUG } - pimpl->mProgressHandler->UpdatePostProcess( static_cast(pimpl->mPostProcessingSteps.size()), + pimpl->mProgressHandler->UpdatePostProcess( static_cast(pimpl->mPostProcessingSteps.size()), static_cast(pimpl->mPostProcessingSteps.size()) ); // update private scene flags @@ -845,14 +845,14 @@ const aiScene* Importer::ApplyPostProcessing(unsigned int pFlags) { ASSIMP_LOG_INFO("Leaving post processing pipeline"); ASSIMP_END_EXCEPTION_REGION(const aiScene*); - + return pimpl->mScene; } // ------------------------------------------------------------------------------------------------ const aiScene* Importer::ApplyCustomizedPostProcessing( BaseProcess *rootProcess, bool requestValidation ) { ai_assert(nullptr != pimpl); - + ASSIMP_BEGIN_EXCEPTION_REGION(); // Return immediately if no scene is active @@ -934,14 +934,14 @@ bool Importer::IsExtensionSupported(const char* szExtension) const { // ------------------------------------------------------------------------------------------------ size_t Importer::GetImporterCount() const { ai_assert(nullptr != pimpl); - + return pimpl->mImporter.size(); } // ------------------------------------------------------------------------------------------------ const aiImporterDesc* Importer::GetImporterInfo(size_t index) const { ai_assert(nullptr != pimpl); - + if (index >= pimpl->mImporter.size()) { return nullptr; } @@ -952,7 +952,7 @@ const aiImporterDesc* Importer::GetImporterInfo(size_t index) const { // ------------------------------------------------------------------------------------------------ BaseImporter* Importer::GetImporter (size_t index) const { ai_assert(nullptr != pimpl); - + if (index >= pimpl->mImporter.size()) { return nullptr; } @@ -963,7 +963,7 @@ BaseImporter* Importer::GetImporter (size_t index) const { // Find a loader plugin for a given file extension BaseImporter* Importer::GetImporter (const char* szExtension) const { ai_assert(nullptr != pimpl); - + return GetImporter(GetImporterIndex(szExtension)); } @@ -1002,7 +1002,7 @@ size_t Importer::GetImporterIndex (const char* szExtension) const { // Helper function to build a list of all file extensions supported by ASSIMP void Importer::GetExtensionList(aiString& szOut) const { ai_assert(nullptr != pimpl); - + ASSIMP_BEGIN_EXCEPTION_REGION(); std::set str; for (std::vector::const_iterator i = pimpl->mImporter.begin();i != pimpl->mImporter.end();++i) { @@ -1028,7 +1028,7 @@ void Importer::GetExtensionList(aiString& szOut) const { // Set a configuration property bool Importer::SetPropertyInteger(const char* szName, int iValue) { ai_assert(nullptr != pimpl); - + bool existing; ASSIMP_BEGIN_EXCEPTION_REGION(); existing = SetGenericProperty(pimpl->mIntProperties, szName,iValue); @@ -1040,7 +1040,7 @@ bool Importer::SetPropertyInteger(const char* szName, int iValue) { // Set a configuration property bool Importer::SetPropertyFloat(const char* szName, ai_real iValue) { ai_assert(nullptr != pimpl); - + bool existing; ASSIMP_BEGIN_EXCEPTION_REGION(); existing = SetGenericProperty(pimpl->mFloatProperties, szName,iValue); @@ -1052,7 +1052,7 @@ bool Importer::SetPropertyFloat(const char* szName, ai_real iValue) { // Set a configuration property bool Importer::SetPropertyString(const char* szName, const std::string& value) { ai_assert(nullptr != pimpl); - + bool existing; ASSIMP_BEGIN_EXCEPTION_REGION(); existing = SetGenericProperty(pimpl->mStringProperties, szName,value); @@ -1064,7 +1064,7 @@ bool Importer::SetPropertyString(const char* szName, const std::string& value) { // Set a configuration property bool Importer::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value) { ai_assert(nullptr != pimpl); - + bool existing; ASSIMP_BEGIN_EXCEPTION_REGION(); existing = SetGenericProperty(pimpl->mMatrixProperties, szName,value); @@ -1076,7 +1076,7 @@ bool Importer::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value) { // Get a configuration property int Importer::GetPropertyInteger(const char* szName, int iErrorReturn /*= 0xffffffff*/) const { ai_assert(nullptr != pimpl); - + return GetGenericProperty(pimpl->mIntProperties,szName,iErrorReturn); } @@ -1084,7 +1084,7 @@ int Importer::GetPropertyInteger(const char* szName, int iErrorReturn /*= 0xffff // Get a configuration property ai_real Importer::GetPropertyFloat(const char* szName, ai_real iErrorReturn /*= 10e10*/) const { ai_assert(nullptr != pimpl); - + return GetGenericProperty(pimpl->mFloatProperties,szName,iErrorReturn); } @@ -1092,7 +1092,7 @@ ai_real Importer::GetPropertyFloat(const char* szName, ai_real iErrorReturn /*= // Get a configuration property std::string Importer::GetPropertyString(const char* szName, const std::string& iErrorReturn /*= ""*/) const { ai_assert(nullptr != pimpl); - + return GetGenericProperty(pimpl->mStringProperties,szName,iErrorReturn); } @@ -1100,13 +1100,13 @@ std::string Importer::GetPropertyString(const char* szName, const std::string& i // Get a configuration property aiMatrix4x4 Importer::GetPropertyMatrix(const char* szName, const aiMatrix4x4& iErrorReturn /*= aiMatrix4x4()*/) const { ai_assert(nullptr != pimpl); - + return GetGenericProperty(pimpl->mMatrixProperties,szName,iErrorReturn); } // ------------------------------------------------------------------------------------------------ // Get the memory requirements of a single node -inline +inline void AddNodeWeight(unsigned int& iScene,const aiNode* pcNode) { if ( nullptr == pcNode ) { return; @@ -1124,7 +1124,7 @@ void AddNodeWeight(unsigned int& iScene,const aiNode* pcNode) { // Get the memory requirements of the scene void Importer::GetMemoryRequirements(aiMemoryInfo& in) const { ai_assert(nullptr != pimpl); - + in = aiMemoryInfo(); aiScene* mScene = pimpl->mScene; diff --git a/code/Common/Importer.h b/code/Common/Importer.h index e7da7f439..0e04f9452 100644 --- a/code/Common/Importer.h +++ b/code/Common/Importer.h @@ -183,7 +183,7 @@ public: // ------------------------------------------------------------------- /** Construct a batch loader from a given IO system to be used - * to access external files + * to access external files */ explicit BatchLoader(IOSystem* pIO, bool validate = false ); @@ -197,13 +197,13 @@ public: * @param enable True for validation. */ void setValidation( bool enabled ); - + // ------------------------------------------------------------------- /** Returns the current validation step. * @return The current validation step. */ bool getValidation() const; - + // ------------------------------------------------------------------- /** Add a new file to the list of files to be loaded. * @param file File to be loaded diff --git a/code/Common/ImporterRegistry.cpp b/code/Common/ImporterRegistry.cpp index ddfcf6798..5df096166 100644 --- a/code/Common/ImporterRegistry.cpp +++ b/code/Common/ImporterRegistry.cpp @@ -205,8 +205,8 @@ corresponding preprocessor flag to selectively disable formats. namespace Assimp { // ------------------------------------------------------------------------------------------------ -void GetImporterInstanceList(std::vector &out) { - +void GetImporterInstanceList(std::vector &out) { + // Some importers may be unimplemented or otherwise unsuitable for general use // in their current state. Devs can set ASSIMP_ENABLE_DEV_IMPORTERS in their // local environment to enable them, otherwise they're left out of the registry. diff --git a/code/Common/RemoveComments.cpp b/code/Common/RemoveComments.cpp index d1c2ac391..e1ba99761 100644 --- a/code/Common/RemoveComments.cpp +++ b/code/Common/RemoveComments.cpp @@ -59,13 +59,16 @@ void CommentRemover::RemoveLineComments(const char* szComment, ai_assert(nullptr != szBuffer); ai_assert(*szComment); - const size_t len = strlen(szComment); + size_t len = strlen(szComment); + const size_t lenBuffer = strlen(szBuffer); + if (len > lenBuffer) { + len = lenBuffer; + } while (*szBuffer) { // skip over quotes if (*szBuffer == '\"' || *szBuffer == '\'') while (*szBuffer++ && *szBuffer != '\"' && *szBuffer != '\''); - if (!strncmp(szBuffer,szComment,len)) { while (!IsLineEnd(*szBuffer)) *szBuffer++ = chReplacement; diff --git a/code/Common/SceneCombiner.cpp b/code/Common/SceneCombiner.cpp index 555d46b6a..8f10d6308 100644 --- a/code/Common/SceneCombiner.cpp +++ b/code/Common/SceneCombiner.cpp @@ -406,11 +406,25 @@ void SceneCombiner::MergeScenes(aiScene **_dest, aiScene *master, std::vector, // where n is the index of the texture. - aiString &s = *((aiString *)prop->mData); + // Copy here because we overwrite the string data in-place and the buffer inside of aiString + // will be a lie if we just reinterpret from prop->mData. The size of mData is not guaranteed to be + // MAXLEN in size. + aiString s(*(aiString *)prop->mData); if ('*' == s.data[0]) { // Offset the index and write it back .. const unsigned int idx = strtoul10(&s.data[1]) + offset[n]; - ASSIMP_itoa10(&s.data[1], sizeof(s.data) - 1, idx); + const unsigned int oldLen = s.length; + + s.length = 1 + ASSIMP_itoa10(&s.data[1], sizeof(s.data) - 1, idx); + + // The string changed in size so we need to reallocate the buffer for the property. + if (oldLen < s.length) { + prop->mDataLength += s.length - oldLen; + delete[] prop->mData; + prop->mData = new char[prop->mDataLength]; + } + + memcpy(prop->mData, static_cast(&s), prop->mDataLength); } } diff --git a/code/Common/ScenePreprocessor.cpp b/code/Common/ScenePreprocessor.cpp index 132b32df7..2ea17c643 100644 --- a/code/Common/ScenePreprocessor.cpp +++ b/code/Common/ScenePreprocessor.cpp @@ -89,6 +89,9 @@ void ScenePreprocessor::ProcessScene() { ASSIMP_LOG_DEBUG("ScenePreprocessor: Adding default material \'" AI_DEFAULT_MATERIAL_NAME "\'"); for (unsigned int i = 0; i < scene->mNumMeshes; ++i) { + if (nullptr == scene->mMeshes[i]) { + continue; + } scene->mMeshes[i]->mMaterialIndex = scene->mNumMaterials; } diff --git a/code/Common/Win32DebugLogStream.h b/code/Common/Win32DebugLogStream.h index 3385aa8ce..51f1ab178 100644 --- a/code/Common/Win32DebugLogStream.h +++ b/code/Common/Win32DebugLogStream.h @@ -71,19 +71,19 @@ public: }; // --------------------------------------------------------------------------- -inline -Win32DebugLogStream::Win32DebugLogStream(){ +inline +Win32DebugLogStream::Win32DebugLogStream(){ // empty } // --------------------------------------------------------------------------- -inline +inline Win32DebugLogStream::~Win32DebugLogStream(){ // empty } // --------------------------------------------------------------------------- -inline +inline void Win32DebugLogStream::write(const char* message) { ::OutputDebugStringA( message); } diff --git a/code/Common/material.cpp b/code/Common/material.cpp index 5230c8b43..6c90e66f0 100644 --- a/code/Common/material.cpp +++ b/code/Common/material.cpp @@ -47,10 +47,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include // ------------------------------------------------------------------------------- -const char* TextureTypeToString(aiTextureType in) -{ - switch (in) - { +const char *TextureTypeToString(aiTextureType in) { + switch (in) { case aiTextureType_NONE: return "n/a"; case aiTextureType_DIFFUSE: @@ -87,6 +85,12 @@ const char* TextureTypeToString(aiTextureType in) return "DiffuseRoughness"; case aiTextureType_AMBIENT_OCCLUSION: return "AmbientOcclusion"; + case aiTextureType_SHEEN: + return "Sheen"; + case aiTextureType_CLEARCOAT: + return "Clearcoat"; + case aiTextureType_TRANSMISSION: + return "Transmission"; case aiTextureType_UNKNOWN: return "Unknown"; default: diff --git a/code/Material/MaterialSystem.cpp b/code/Material/MaterialSystem.cpp index c35a1aa93..23d198953 100644 --- a/code/Material/MaterialSystem.cpp +++ b/code/Material/MaterialSystem.cpp @@ -555,17 +555,23 @@ uint32_t Assimp::ComputeMaterialHash(const aiMaterial *mat, bool includeMatName } // ------------------------------------------------------------------------------------------------ -void aiMaterial::CopyPropertyList(aiMaterial *pcDest, +void aiMaterial::CopyPropertyList(aiMaterial *const pcDest, const aiMaterial *pcSrc) { ai_assert(nullptr != pcDest); ai_assert(nullptr != pcSrc); + ai_assert(pcDest->mNumProperties <= pcDest->mNumAllocated); + ai_assert(pcSrc->mNumProperties <= pcSrc->mNumAllocated); - unsigned int iOldNum = pcDest->mNumProperties; + const unsigned int iOldNum = pcDest->mNumProperties; pcDest->mNumAllocated += pcSrc->mNumAllocated; pcDest->mNumProperties += pcSrc->mNumProperties; + const unsigned int numAllocated = pcDest->mNumAllocated; aiMaterialProperty **pcOld = pcDest->mProperties; - pcDest->mProperties = new aiMaterialProperty *[pcDest->mNumAllocated]; + pcDest->mProperties = new aiMaterialProperty *[numAllocated]; + + ai_assert(!iOldNum || pcOld); + ai_assert(iOldNum < numAllocated); if (iOldNum && pcOld) { for (unsigned int i = 0; i < iOldNum; ++i) { diff --git a/code/Pbrt/PbrtExporter.cpp b/code/Pbrt/PbrtExporter.cpp index b793c37f9..1c7024c28 100644 --- a/code/Pbrt/PbrtExporter.cpp +++ b/code/Pbrt/PbrtExporter.cpp @@ -83,8 +83,7 @@ Other: #include #include -#define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" +#include "stb/stb_image.h" using namespace Assimp; @@ -106,14 +105,13 @@ void ExportScenePbrt ( } // end of namespace Assimp // Constructor -PbrtExporter::PbrtExporter ( - const aiScene* pScene, IOSystem* pIOSystem, - const std::string path, const std::string file) -: mScene(pScene), - mIOSystem(pIOSystem), - mPath(path), - mFile(file) -{ +PbrtExporter::PbrtExporter( + const aiScene *pScene, IOSystem *pIOSystem, + const std::string &path, const std::string &file) : + mScene(pScene), + mIOSystem(pIOSystem), + mPath(path), + mFile(file) { // Export embedded textures. if (mScene->mNumTextures > 0) if (!mIOSystem->CreateDirectory("textures")) @@ -210,12 +208,12 @@ void PbrtExporter::WriteMetaData() { aiString* value = static_cast(pMetaData->mValues[i].mData); std::string svalue = value->C_Str(); - std::size_t found = svalue.find_first_of("\n"); + std::size_t found = svalue.find_first_of('\n'); mOutput << "\n"; while (found != std::string::npos) { mOutput << "# " << svalue.substr(0, found) << "\n"; svalue = svalue.substr(found + 1); - found = svalue.find_first_of("\n"); + found = svalue.find_first_of('\n'); } mOutput << "# " << svalue << "\n"; break; @@ -596,8 +594,8 @@ void PbrtExporter::WriteMaterial(int m) { } mOutput << "\n"; - auto White = [](aiColor3D c) { return c.r == 1 && c.g == 1 && c.b == 1; }; - auto Black = [](aiColor3D c) { return c.r == 0 && c.g == 0 && c.b == 0; }; + auto White = [](const aiColor3D &c) { return c.r == 1 && c.g == 1 && c.b == 1; }; + auto Black = [](const aiColor3D &c) { return c.r == 0 && c.g == 0 && c.b == 0; }; aiColor3D diffuse, specular, transparency; bool constantDiffuse = (material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse) == AI_SUCCESS && diff --git a/code/Pbrt/PbrtExporter.h b/code/Pbrt/PbrtExporter.h index 167f318fc..e8ff03ccb 100644 --- a/code/Pbrt/PbrtExporter.h +++ b/code/Pbrt/PbrtExporter.h @@ -74,8 +74,8 @@ class PbrtExporter { public: /// Constructor for a specific scene to export - PbrtExporter(const aiScene* pScene, IOSystem* pIOSystem, - const std::string path, const std::string file); + PbrtExporter(const aiScene *pScene, IOSystem *pIOSystem, + const std::string &path, const std::string &file); /// Destructor virtual ~PbrtExporter(); diff --git a/code/PostProcessing/ArmaturePopulate.h b/code/PostProcessing/ArmaturePopulate.h index 08a72bd66..ded8b4886 100644 --- a/code/PostProcessing/ArmaturePopulate.h +++ b/code/PostProcessing/ArmaturePopulate.h @@ -97,7 +97,7 @@ public: static void BuildBoneList(aiNode *current_node, const aiNode *root_node, const aiScene *scene, - std::vector &bones); + std::vector &bones); static void BuildBoneStack(aiNode *current_node, const aiNode *root_node, const aiScene *scene, diff --git a/code/PostProcessing/DropFaceNormalsProcess.cpp b/code/PostProcessing/DropFaceNormalsProcess.cpp index c01ac35e5..21abf9693 100644 --- a/code/PostProcessing/DropFaceNormalsProcess.cpp +++ b/code/PostProcessing/DropFaceNormalsProcess.cpp @@ -104,7 +104,7 @@ bool DropFaceNormalsProcess::DropMeshFaceNormals (aiMesh* mesh) { if (nullptr == mesh->mNormals) { return false; } - + delete[] mesh->mNormals; mesh->mNormals = nullptr; return true; diff --git a/code/PostProcessing/EmbedTexturesProcess.cpp b/code/PostProcessing/EmbedTexturesProcess.cpp index 7e435e556..d7720de98 100644 --- a/code/PostProcessing/EmbedTexturesProcess.cpp +++ b/code/PostProcessing/EmbedTexturesProcess.cpp @@ -4,7 +4,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2021, assimp team - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -41,6 +40,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "EmbedTexturesProcess.h" +#include +#include #include #include "ProcessHelper.h" @@ -48,11 +49,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; -EmbedTexturesProcess::EmbedTexturesProcess() -: BaseProcess() { +EmbedTexturesProcess::EmbedTexturesProcess() : + BaseProcess() { + // empty } EmbedTexturesProcess::~EmbedTexturesProcess() { + // empty } bool EmbedTexturesProcess::IsActive(unsigned int pFlags) const { @@ -62,15 +65,16 @@ bool EmbedTexturesProcess::IsActive(unsigned int pFlags) const { void EmbedTexturesProcess::SetupProperties(const Importer* pImp) { mRootPath = pImp->GetPropertyString("sourceFilePath"); mRootPath = mRootPath.substr(0, mRootPath.find_last_of("\\/") + 1u); + mIOHandler = pImp->GetIOHandler(); } void EmbedTexturesProcess::Execute(aiScene* pScene) { - if (pScene == nullptr || pScene->mRootNode == nullptr) return; + if (pScene == nullptr || pScene->mRootNode == nullptr || mIOHandler == nullptr){ + return; + } aiString path; - uint32_t embeddedTexturesCount = 0u; - for (auto matId = 0u; matId < pScene->mNumMaterials; ++matId) { auto material = pScene->mMaterials[matId]; @@ -96,32 +100,36 @@ void EmbedTexturesProcess::Execute(aiScene* pScene) { ASSIMP_LOG_INFO("EmbedTexturesProcess finished. Embedded ", embeddedTexturesCount, " textures." ); } -bool EmbedTexturesProcess::addTexture(aiScene* pScene, std::string path) const { +bool EmbedTexturesProcess::addTexture(aiScene *pScene, const std::string &path) const { std::streampos imageSize = 0; std::string imagePath = path; // Test path directly - std::ifstream file(imagePath, std::ios::binary | std::ios::ate); - if ((imageSize = file.tellg()) == std::streampos(-1)) { + if (!mIOHandler->Exists(imagePath)) { ASSIMP_LOG_WARN("EmbedTexturesProcess: Cannot find image: ", imagePath, ". Will try to find it in root folder."); // Test path in root path imagePath = mRootPath + path; - file.open(imagePath, std::ios::binary | std::ios::ate); - if ((imageSize = file.tellg()) == std::streampos(-1)) { + if (!mIOHandler->Exists(imagePath)) { // Test path basename in root path imagePath = mRootPath + path.substr(path.find_last_of("\\/") + 1u); - file.open(imagePath, std::ios::binary | std::ios::ate); - if ((imageSize = file.tellg()) == std::streampos(-1)) { + if (!mIOHandler->Exists(imagePath)) { ASSIMP_LOG_ERROR("EmbedTexturesProcess: Unable to embed texture: ", path, "."); return false; } } } + IOStream* pFile = mIOHandler->Open(imagePath); + if (pFile == nullptr) { + ASSIMP_LOG_ERROR("EmbedTexturesProcess: Unable to embed texture: ", path, "."); + return false; + } + imageSize = pFile->FileSize(); aiTexel* imageContent = new aiTexel[ 1ul + static_cast( imageSize ) / sizeof(aiTexel)]; - file.seekg(0, std::ios::beg); - file.read(reinterpret_cast(imageContent), imageSize); + pFile->Seek(0, aiOrigin_SET); + pFile->Read(reinterpret_cast(imageContent), imageSize, 1); + mIOHandler->Close(pFile); // Enlarging the textures table unsigned int textureId = pScene->mNumTextures++; @@ -129,7 +137,7 @@ bool EmbedTexturesProcess::addTexture(aiScene* pScene, std::string path) const { pScene->mTextures = new aiTexture*[pScene->mNumTextures]; ::memmove(pScene->mTextures, oldTextures, sizeof(aiTexture*) * (pScene->mNumTextures - 1u)); delete [] oldTextures; - + // Add the new texture auto pTexture = new aiTexture; pTexture->mHeight = 0; // Means that this is still compressed diff --git a/code/PostProcessing/EmbedTexturesProcess.h b/code/PostProcessing/EmbedTexturesProcess.h index 90970937a..b33968850 100644 --- a/code/PostProcessing/EmbedTexturesProcess.h +++ b/code/PostProcessing/EmbedTexturesProcess.h @@ -48,6 +48,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. struct aiNode; +class IOSystem; + namespace Assimp { /** @@ -76,10 +78,11 @@ public: private: // Resolve the path and add the file content to the scene as a texture. - bool addTexture(aiScene* pScene, std::string path) const; + bool addTexture(aiScene *pScene, const std::string &path) const; private: std::string mRootPath; + IOSystem* mIOHandler = nullptr; }; } // namespace Assimp diff --git a/code/PostProcessing/FindDegenerates.cpp b/code/PostProcessing/FindDegenerates.cpp index eae91ff02..3809abf50 100644 --- a/code/PostProcessing/FindDegenerates.cpp +++ b/code/PostProcessing/FindDegenerates.cpp @@ -90,7 +90,7 @@ void FindDegeneratesProcess::Execute( aiScene* pScene) { if ( nullptr == pScene) { return; } - + std::unordered_map meshMap; meshMap.reserve(pScene->mNumMeshes); diff --git a/code/PostProcessing/FindInstancesProcess.cpp b/code/PostProcessing/FindInstancesProcess.cpp index ab5f52b78..d46afc201 100644 --- a/code/PostProcessing/FindInstancesProcess.cpp +++ b/code/PostProcessing/FindInstancesProcess.cpp @@ -137,7 +137,7 @@ void FindInstancesProcess::Execute( aiScene* pScene) aiMesh* inst = pScene->mMeshes[i]; hashes[i] = GetMeshHash(inst); - // Find an appropriate epsilon + // Find an appropriate epsilon // to compare position differences against float epsilon = ComputePositionEpsilon(inst); epsilon *= epsilon; diff --git a/code/PostProcessing/MakeVerboseFormat.h b/code/PostProcessing/MakeVerboseFormat.h index c1db76dca..4304a3afa 100644 --- a/code/PostProcessing/MakeVerboseFormat.h +++ b/code/PostProcessing/MakeVerboseFormat.h @@ -98,7 +98,7 @@ public: // ------------------------------------------------------------------- /** Checks whether the scene is already in verbose format. - * @param pScene The data to check. + * @param pScene The data to check. * @return true if the scene is already in verbose format. */ static bool IsVerboseFormat(const aiScene* pScene); diff --git a/code/PostProcessing/OptimizeGraph.cpp b/code/PostProcessing/OptimizeGraph.cpp index e33c2ab18..d7bcf3fec 100644 --- a/code/PostProcessing/OptimizeGraph.cpp +++ b/code/PostProcessing/OptimizeGraph.cpp @@ -170,7 +170,7 @@ void OptimizeGraphProcess::CollectNewChildren(aiNode *nd, std::list &n ++it; } if (join_master && !join.empty()) { - join_master->mName.length = ::ai_snprintf(join_master->mName.data, MAXLEN, "$MergedNode_%i", count_merged++); + join_master->mName.length = ::ai_snprintf(join_master->mName.data, MAXLEN, "$MergedNode_%u", count_merged++); unsigned int out_meshes = 0; for (std::list::const_iterator it = join.cbegin(); it != join.cend(); ++it) { diff --git a/code/PostProcessing/PretransformVertices.cpp b/code/PostProcessing/PretransformVertices.cpp index e9a3af0d2..fa95319ff 100644 --- a/code/PostProcessing/PretransformVertices.cpp +++ b/code/PostProcessing/PretransformVertices.cpp @@ -481,7 +481,7 @@ void PretransformVertices::Execute(aiScene *pScene) { pScene->mMeshes[i]->mNumBones = 0; } } else { - apcOutMeshes.reserve(pScene->mNumMaterials << 1u); + apcOutMeshes.reserve(static_cast(pScene->mNumMaterials) << 1u); std::list aiVFormats; std::vector s(pScene->mNumMeshes, 0); diff --git a/code/PostProcessing/RemoveRedundantMaterials.cpp b/code/PostProcessing/RemoveRedundantMaterials.cpp index c252f37a5..36745fb1d 100644 --- a/code/PostProcessing/RemoveRedundantMaterials.cpp +++ b/code/PostProcessing/RemoveRedundantMaterials.cpp @@ -215,7 +215,7 @@ void RemoveRedundantMatsProcess::Execute( aiScene* pScene) } else { - ASSIMP_LOG_INFO("RemoveRedundantMatsProcess finished. Removed ", redundantRemoved, " redundant and ", + ASSIMP_LOG_INFO("RemoveRedundantMatsProcess finished. Removed ", redundantRemoved, " redundant and ", unreferencedRemoved, " unused materials."); } } diff --git a/code/PostProcessing/ScaleProcess.cpp b/code/PostProcessing/ScaleProcess.cpp index 0a3e29c42..63dd0443d 100644 --- a/code/PostProcessing/ScaleProcess.cpp +++ b/code/PostProcessing/ScaleProcess.cpp @@ -75,7 +75,7 @@ void ScaleProcess::SetupProperties( const Importer* pImp ) { // File scaling * Application Scaling float importerScale = pImp->GetPropertyFloat( AI_CONFIG_APP_SCALE_KEY, 1.0f ); - // apply scale to the scale + // apply scale to the scale // helps prevent bugs with backward compatibility for anyone using normal scaling. mScale *= importerScale; } @@ -84,7 +84,7 @@ void ScaleProcess::Execute( aiScene* pScene ) { if(mScale == 1.0f) { return; // nothing to scale } - + ai_assert( mScale != 0 ); ai_assert( nullptr != pScene ); ai_assert( nullptr != pScene->mRootNode ); @@ -96,7 +96,7 @@ void ScaleProcess::Execute( aiScene* pScene ) { if ( nullptr == pScene->mRootNode ) { return; } - + // Process animations and update position transform to new unit system for( unsigned int animationID = 0; animationID < pScene->mNumAnimations; animationID++ ) { @@ -105,7 +105,7 @@ void ScaleProcess::Execute( aiScene* pScene ) { for( unsigned int animationChannel = 0; animationChannel < animation->mNumChannels; animationChannel++) { aiNodeAnim* anim = animation->mChannels[animationChannel]; - + for( unsigned int posKey = 0; posKey < anim->mNumPositionKeys; posKey++) { aiVectorKey& vectorKey = anim->mPositionKeys[posKey]; @@ -116,8 +116,8 @@ void ScaleProcess::Execute( aiScene* pScene ) { for( unsigned int meshID = 0; meshID < pScene->mNumMeshes; meshID++) { - aiMesh *mesh = pScene->mMeshes[meshID]; - + aiMesh *mesh = pScene->mMeshes[meshID]; + // Reconstruct mesh vertexes to the new unit system for( unsigned int vertexID = 0; vertexID < mesh->mNumVertices; vertexID++) { @@ -129,9 +129,9 @@ void ScaleProcess::Execute( aiScene* pScene ) { // bone placement / scaling for( unsigned int boneID = 0; boneID < mesh->mNumBones; boneID++) { - // Reconstruct matrix by transform rather than by scale + // Reconstruct matrix by transform rather than by scale // This prevent scale values being changed which can - // be meaningful in some cases + // be meaningful in some cases // like when you want the modeller to see 1:1 compatibility. aiBone* bone = mesh->mBones[boneID]; @@ -139,10 +139,10 @@ void ScaleProcess::Execute( aiScene* pScene ) { aiQuaternion rotation; bone->mOffsetMatrix.Decompose( scale, rotation, pos); - + aiMatrix4x4 translation; aiMatrix4x4::Translation( pos * mScale, translation ); - + aiMatrix4x4 scaling; aiMatrix4x4::Scaling( aiVector3D(scale), scaling ); @@ -157,7 +157,7 @@ void ScaleProcess::Execute( aiScene* pScene ) { for( unsigned int animMeshID = 0; animMeshID < mesh->mNumAnimMeshes; animMeshID++) { aiAnimMesh * animMesh = mesh->mAnimMeshes[animMeshID]; - + for( unsigned int vertexID = 0; vertexID < animMesh->mNumVertices; vertexID++) { aiVector3D& vertex = animMesh->mVertices[vertexID]; @@ -169,31 +169,31 @@ void ScaleProcess::Execute( aiScene* pScene ) { traverseNodes( pScene->mRootNode ); } -void ScaleProcess::traverseNodes( aiNode *node, unsigned int nested_node_id ) { +void ScaleProcess::traverseNodes( aiNode *node, unsigned int nested_node_id ) { applyScaling( node ); for( size_t i = 0; i < node->mNumChildren; i++) { // recurse into the tree until we are done! - traverseNodes( node->mChildren[i], nested_node_id+1 ); + traverseNodes( node->mChildren[i], nested_node_id+1 ); } } void ScaleProcess::applyScaling( aiNode *currentNode ) { if ( nullptr != currentNode ) { - // Reconstruct matrix by transform rather than by scale + // Reconstruct matrix by transform rather than by scale // This prevent scale values being changed which can - // be meaningful in some cases - // like when you want the modeller to + // be meaningful in some cases + // like when you want the modeller to // see 1:1 compatibility. - + aiVector3D pos, scale; aiQuaternion rotation; currentNode->mTransformation.Decompose( scale, rotation, pos); - + aiMatrix4x4 translation; aiMatrix4x4::Translation( pos * mScale, translation ); - + aiMatrix4x4 scaling; // note: we do not use mScale here, this is on purpose. diff --git a/code/PostProcessing/ScaleProcess.h b/code/PostProcessing/ScaleProcess.h index d88490b1c..4dc7e3559 100644 --- a/code/PostProcessing/ScaleProcess.h +++ b/code/PostProcessing/ScaleProcess.h @@ -55,8 +55,8 @@ namespace Assimp { // --------------------------------------------------------------------------- /** ScaleProcess: Class to rescale the whole model. * Now rescales animations, bones, and blend shapes properly. - * Please note this will not write to 'scale' transform it will rewrite mesh - * and matrixes so that your scale values + * Please note this will not write to 'scale' transform it will rewrite mesh + * and matrixes so that your scale values * from your model package are preserved, so this is completely intentional * bugs should be reported as soon as they are found. */ diff --git a/code/PostProcessing/SortByPTypeProcess.cpp b/code/PostProcessing/SortByPTypeProcess.cpp index 20ab63249..3787be51e 100644 --- a/code/PostProcessing/SortByPTypeProcess.cpp +++ b/code/PostProcessing/SortByPTypeProcess.cpp @@ -127,7 +127,7 @@ void SortByPTypeProcess::Execute(aiScene *pScene) { unsigned int aiNumMeshesPerPType[4] = { 0, 0, 0, 0 }; std::vector outMeshes; - outMeshes.reserve(pScene->mNumMeshes << 1u); + outMeshes.reserve(static_cast(pScene->mNumMeshes) << 1u); bool bAnyChanges = false; diff --git a/code/PostProcessing/SplitByBoneCountProcess.cpp b/code/PostProcessing/SplitByBoneCountProcess.cpp index 2613d8561..ed5b9411e 100644 --- a/code/PostProcessing/SplitByBoneCountProcess.cpp +++ b/code/PostProcessing/SplitByBoneCountProcess.cpp @@ -209,7 +209,7 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vector newBonesAtCurrentFace; - + const aiFace& face = pMesh->mFaces[a]; // check every vertex if its bones would still fit into the current submesh for( unsigned int b = 0; b < face.mNumIndices; ++b ) @@ -221,7 +221,7 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vectormNumAnimMeshes > 0) { newMesh->mNumAnimMeshes = pMesh->mNumAnimMeshes; newMesh->mAnimMeshes = new aiAnimMesh*[newMesh->mNumAnimMeshes]; - + for (unsigned int morphIdx = 0; morphIdx < newMesh->mNumAnimMeshes; ++morphIdx) { aiAnimMesh* origTarget = pMesh->mAnimMeshes[morphIdx]; aiAnimMesh* newTarget = new aiAnimMesh; @@ -421,16 +421,16 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vectormNumVertices = numSubMeshVertices; newTarget->mVertices = new aiVector3D[numSubMeshVertices]; newMesh->mAnimMeshes[morphIdx] = newTarget; - + if (origTarget->HasNormals()) { newTarget->mNormals = new aiVector3D[numSubMeshVertices]; } - + if (origTarget->HasTangentsAndBitangents()) { newTarget->mTangents = new aiVector3D[numSubMeshVertices]; newTarget->mBitangents = new aiVector3D[numSubMeshVertices]; } - + for( unsigned int vi = 0; vi < numSubMeshVertices; ++vi) { // find the source vertex for it in the source mesh unsigned int previousIndex = previousVertexIndices[vi]; diff --git a/code/PostProcessing/SplitLargeMeshes.cpp b/code/PostProcessing/SplitLargeMeshes.cpp index ed680cf7f..cb614edaa 100644 --- a/code/PostProcessing/SplitLargeMeshes.cpp +++ b/code/PostProcessing/SplitLargeMeshes.cpp @@ -40,7 +40,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** +/** * @file Implementation of the SplitLargeMeshes postprocessing step */ @@ -353,7 +353,7 @@ void SplitLargeMeshesProcess_Vertex::Execute( aiScene* pScene) { std::vector > avList; - //Check for point cloud first, + //Check for point cloud first, //Do not process point cloud, splitMesh works only with faces data for (unsigned int a = 0; a < pScene->mNumMeshes; a++) { if ( pScene->mMeshes[a]->mPrimitiveTypes == aiPrimitiveType_POINT ) { diff --git a/code/PostProcessing/TextureTransform.cpp b/code/PostProcessing/TextureTransform.cpp index 681b047c0..74b00d92c 100644 --- a/code/PostProcessing/TextureTransform.cpp +++ b/code/PostProcessing/TextureTransform.cpp @@ -448,7 +448,7 @@ void TextureTransformStep::Execute( aiScene* pScene) if (size > AI_MAX_NUMBER_OF_TEXTURECOORDS) { if (!DefaultLogger::isNullLogger()) { - ASSIMP_LOG_ERROR(static_cast(trafo.size()), " UV channels required but just ", + ASSIMP_LOG_ERROR(static_cast(trafo.size()), " UV channels required but just ", AI_MAX_NUMBER_OF_TEXTURECOORDS, " available"); } size = AI_MAX_NUMBER_OF_TEXTURECOORDS; diff --git a/code/PostProcessing/TriangulateProcess.cpp b/code/PostProcessing/TriangulateProcess.cpp index 0f71320b8..b7928ee59 100644 --- a/code/PostProcessing/TriangulateProcess.cpp +++ b/code/PostProcessing/TriangulateProcess.cpp @@ -86,11 +86,11 @@ namespace { /** * @brief Encode the current triangle, and make sure it is recognized as a triangle. - * + * * This method will rotate indices in tri if needed in order to avoid tri to be considered * part of the previous ngon. This method is to be used whenever you want to emit a real triangle, * and make sure it is seen as a triangle. - * + * * @param tri Triangle to encode. */ void ngonEncodeTriangle(aiFace * tri) { @@ -108,10 +108,10 @@ namespace { /** * @brief Encode a quad (2 triangles) in ngon encoding, and make sure they are seen as a single ngon. - * + * * @param tri1 First quad triangle * @param tri2 Second quad triangle - * + * * @pre Triangles must be properly fanned from the most appropriate vertex. */ void ngonEncodeQuad(aiFace *tri1, aiFace *tri2) { @@ -140,7 +140,7 @@ namespace { /** * @brief Check whether this triangle would be considered part of the lastly emitted ngon or not. - * + * * @param tri Current triangle. * @return true If used as is, this triangle will be part of last ngon. * @return false If used as is, this triangle is not considered part of the last ngon. @@ -512,7 +512,7 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) num = 0; break; - /*curOut -= (max-num); // undo all previous work + /*curOut -= (max-num); // undo all previous work for (tmp = 0; tmp < max-2; ++tmp) { aiFace& nface = *curOut++; diff --git a/contrib/draco/.ruby-version b/contrib/draco/.ruby-version deleted file mode 100644 index 276cbf9e2..000000000 --- a/contrib/draco/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -2.3.0 diff --git a/contrib/draco/.travis.yml b/contrib/draco/.travis.yml deleted file mode 100644 index e9ef7123f..000000000 --- a/contrib/draco/.travis.yml +++ /dev/null @@ -1,31 +0,0 @@ -cache: ccache -language: cpp -matrix: - include: - - os: linux - dist: xenial - compiler: clang - - os: linux - dist: xenial - compiler: gcc - - os: osx - compiler: clang - -addons: - apt: - packages: - - cmake - -script: - # Output version info for compilers, cmake, and make - - ${CC} -v - - ${CXX} -v - - cmake --version - - make --version - # Clone googletest - - pushd .. && git clone https://github.com/google/googletest.git && popd - # Configure and build - - mkdir _travis_build && cd _travis_build - - cmake -G "Unix Makefiles" -DENABLE_TESTS=ON .. - - make -j10 - - ./draco_tests diff --git a/contrib/draco/CMakeLists.txt b/contrib/draco/CMakeLists.txt index 3da2c664a..5526e7f60 100644 --- a/contrib/draco/CMakeLists.txt +++ b/contrib/draco/CMakeLists.txt @@ -804,7 +804,7 @@ else() draco_points_enc) # Library targets that consume the object collections. - if(MSVC OR WIN32) + if(MSVC) # In order to produce a DLL and import library the Windows tools require # that the exported symbols are part of the DLL target. The unfortunate side # effect of this is that a single configuration cannot output both the @@ -889,9 +889,6 @@ else() # For Mac, we need to build a .bundle for the unity plugin. if(APPLE) set_target_properties(dracodec_unity PROPERTIES BUNDLE true) - elseif(NOT unity_decoder_lib_type STREQUAL STATIC) - set_target_properties(dracodec_unity - PROPERTIES SOVERSION ${DRACO_SOVERSION}) endif() endif() @@ -916,9 +913,6 @@ else() # For Mac, we need to build a .bundle for the plugin. if(APPLE) set_target_properties(draco_maya_wrapper PROPERTIES BUNDLE true) - else() - set_target_properties(draco_maya_wrapper - PROPERTIES SOVERSION ${DRACO_SOVERSION}) endif() endif() diff --git a/contrib/draco/README.md b/contrib/draco/README.md index add66edcb..0d980b387 100644 --- a/contrib/draco/README.md +++ b/contrib/draco/README.md @@ -2,16 +2,16 @@

-![Build Status: master](https://travis-ci.org/google/draco.svg?branch=master) +[![Build Status](https://github.com/google/draco/workflows/Build/badge.svg)](https://github.com/google/draco/actions?query=workflow%3ABuild) News ======= ### Version 1.4.1 release -* Using the versioned gstatic.com WASM and Javascript decoders is now +* Using the versioned www.gstatic.com WASM and Javascript decoders is now recommended. To use v1.4.1, use this URL: * https://www.gstatic.com/draco/versioned/decoders/1.4.1/* * Replace the * with the files to load. E.g. - * https://gstatic.com/draco/versioned/decoders/1.4.1/draco_decoder.js + * https://www.gstatic.com/draco/versioned/decoders/1.4.1/draco_decoder.js * This works with the v1.3.6 and v1.4.0 releases, and will work with future Draco releases. * Bug fixes diff --git a/contrib/draco/cmake/draco_build_definitions.cmake b/contrib/draco/cmake/draco_build_definitions.cmake index c1ada6206..f7354c15f 100644 --- a/contrib/draco/cmake/draco_build_definitions.cmake +++ b/contrib/draco/cmake/draco_build_definitions.cmake @@ -6,7 +6,7 @@ set(DRACO_CMAKE_DRACO_BUILD_DEFINITIONS_CMAKE_ 1) # Utility for controlling the main draco library dependency. This changes in # shared builds, and when an optional target requires a shared library build. macro(set_draco_target) - if(MSVC OR WIN32) + if(MSVC) set(draco_dependency draco) set(draco_plugin_dependency ${draco_dependency}) else() @@ -63,6 +63,11 @@ macro(draco_set_build_definitions) if(BUILD_SHARED_LIBS) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) endif() + else() + if(${CMAKE_SIZEOF_VOID_P} EQUAL 8) + # Ensure 64-bit platforms can support large files. + list(APPEND draco_defines "_LARGEFILE_SOURCE" "_FILE_OFFSET_BITS=64") + endif() endif() if(ANDROID) @@ -114,4 +119,6 @@ macro(draco_set_build_definitions) draco_check_emscripten_environment() draco_get_required_emscripten_flags(FLAG_LIST_VAR draco_base_cxx_flags) endif() + + draco_configure_sanitizer() endmacro() diff --git a/contrib/draco/cmake/draco_features.cmake b/contrib/draco/cmake/draco_features.cmake deleted file mode 100644 index be444bf24..000000000 --- a/contrib/draco/cmake/draco_features.cmake +++ /dev/null @@ -1,63 +0,0 @@ -if(DRACO_CMAKE_DRACO_FEATURES_CMAKE_) - return() -endif() -set(DRACO_CMAKE_DRACO_FEATURES_CMAKE_ 1) - -set(draco_features_file_name "${draco_build_dir}/draco/draco_features.h") -set(draco_features_list) - -# Macro that handles tracking of Draco preprocessor symbols for the purpose of -# producing draco_features.h. -# -# draco_enable_feature(FEATURE [TARGETS ]) FEATURE -# is required. It should be a Draco preprocessor symbol. TARGETS is optional. It -# can be one or more draco targets. -# -# When the TARGETS argument is not present the preproc symbol is added to -# draco_features.h. When it is draco_features.h is unchanged, and -# target_compile_options() is called for each target specified. -macro(draco_enable_feature) - set(def_flags) - set(def_single_arg_opts FEATURE) - set(def_multi_arg_opts TARGETS) - cmake_parse_arguments(DEF "${def_flags}" "${def_single_arg_opts}" - "${def_multi_arg_opts}" ${ARGN}) - if("${DEF_FEATURE}" STREQUAL "") - message(FATAL_ERROR "Empty FEATURE passed to draco_enable_feature().") - endif() - - # Do nothing/return early if $DEF_FEATURE is already in the list. - list(FIND draco_features_list ${DEF_FEATURE} df_index) - if(NOT df_index EQUAL -1) - return() - endif() - - list(LENGTH DEF_TARGETS df_targets_list_length) - if(${df_targets_list_length} EQUAL 0) - list(APPEND draco_features_list ${DEF_FEATURE}) - else() - foreach(target ${DEF_TARGETS}) - target_compile_definitions(${target} PRIVATE ${DEF_FEATURE}) - endforeach() - endif() -endmacro() - -# Function for generating draco_features.h. -function(draco_generate_features_h) - file(WRITE "${draco_features_file_name}.new" - "// GENERATED FILE -- DO NOT EDIT\n\n" "#ifndef DRACO_FEATURES_H_\n" - "#define DRACO_FEATURES_H_\n\n") - - foreach(feature ${draco_features_list}) - file(APPEND "${draco_features_file_name}.new" "#define ${feature}\n") - endforeach() - - file(APPEND "${draco_features_file_name}.new" - "\n#endif // DRACO_FEATURES_H_") - - # Will replace ${draco_features_file_name} only if the file content has - # changed. This prevents forced Draco rebuilds after CMake runs. - configure_file("${draco_features_file_name}.new" - "${draco_features_file_name}") - file(REMOVE "${draco_features_file_name}.new") -endfunction() diff --git a/contrib/draco/cmake/draco_flags.cmake b/contrib/draco/cmake/draco_flags.cmake index cb9d489e6..0397859a4 100644 --- a/contrib/draco/cmake/draco_flags.cmake +++ b/contrib/draco/cmake/draco_flags.cmake @@ -80,6 +80,12 @@ macro(draco_test_cxx_flag) # Run the actual compile test. unset(draco_all_cxx_flags_pass CACHE) message("--- Running combined CXX flags test, flags: ${all_cxx_flags}") + + # check_cxx_compiler_flag() requires that the flags are a string. When flags + # are passed as a list it will remove the list separators, and attempt to run + # a compile command using list entries concatenated together as a single + # argument. Avoid the problem by forcing the argument to be a string. + draco_set_and_stringify(SOURCE_VARS all_cxx_flags DEST all_cxx_flags) check_cxx_compiler_flag("${all_cxx_flags}" draco_all_cxx_flags_pass) if(cxx_test_FLAG_REQUIRED AND NOT draco_all_cxx_flags_pass) @@ -194,6 +200,9 @@ macro(draco_test_exe_linker_flag) else() unset(CMAKE_EXE_LINKER_FLAGS) endif() + + list(APPEND DRACO_EXE_LINKER_FLAGS ${${link_FLAG_LIST_VAR_NAME}}) + list(REMOVE_DUPLICATES DRACO_EXE_LINKER_FLAGS) endmacro() # Runs the draco compiler tests. This macro builds up the list of list var(s) diff --git a/contrib/draco/cmake/draco_install.cmake b/contrib/draco/cmake/draco_install.cmake index 5c63ecb4a..09bfb591d 100644 --- a/contrib/draco/cmake/draco_install.cmake +++ b/contrib/draco/cmake/draco_install.cmake @@ -55,7 +55,7 @@ macro(draco_setup_install_target) install(TARGETS draco_encoder DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}") - if(WIN32) + if(MSVC) install(TARGETS draco DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") else() diff --git a/contrib/draco/cmake/draco_sanitizer.cmake b/contrib/draco/cmake/draco_sanitizer.cmake index ca8e23176..d2e41a6cb 100644 --- a/contrib/draco/cmake/draco_sanitizer.cmake +++ b/contrib/draco/cmake/draco_sanitizer.cmake @@ -5,28 +5,28 @@ set(DRACO_CMAKE_DRACO_SANITIZER_CMAKE_ 1) # Handles the details of enabling sanitizers. macro(draco_configure_sanitizer) - if(DRACO_SANITIZE AND NOT MSVC) + if(DRACO_SANITIZE AND NOT EMSCRIPTEN AND NOT MSVC) if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") if(DRACO_SANITIZE MATCHES "cfi") - list(APPEND DRACO_CXX_FLAGS "-flto" "-fno-sanitize-trap=cfi") - list(APPEND DRACO_EXE_LINKER_FLAGS "-flto" "-fno-sanitize-trap=cfi" + list(APPEND SAN_CXX_FLAGS "-flto" "-fno-sanitize-trap=cfi") + list(APPEND SAN_LINKER_FLAGS "-flto" "-fno-sanitize-trap=cfi" "-fuse-ld=gold") endif() if(${CMAKE_SIZEOF_VOID_P} EQUAL 4 AND DRACO_SANITIZE MATCHES "integer|undefined") - list(APPEND DRACO_EXE_LINKER_FLAGS "--rtlib=compiler-rt" "-lgcc_s") + list(APPEND SAN_LINKER_FLAGS "--rtlib=compiler-rt" "-lgcc_s") endif() endif() - list(APPEND DRACO_CXX_FLAGS "-fsanitize=${DRACO_SANITIZE}") - list(APPEND DRACO_EXE_LINKER_FLAGS "-fsanitize=${DRACO_SANITIZE}") + list(APPEND SAN_CXX_FLAGS "-fsanitize=${DRACO_SANITIZE}") + list(APPEND SAN_LINKER_FLAGS "-fsanitize=${DRACO_SANITIZE}") # Make sanitizer callstacks accurate. - list(APPEND DRACO_CXX_FLAGS "-fno-omit-frame-pointer" - "-fno-optimize-sibling-calls") + list(APPEND SAN_CXX_FLAGS "-fno-omit-frame-pointer") + list(APPEND SAN_CXX_FLAGS "-fno-optimize-sibling-calls") - draco_test_cxx_flag(FLAG_LIST_VAR_NAMES DRACO_CXX_FLAGS FLAG_REQUIRED) - draco_test_exe_linker_flag(FLAG_LIST_VAR_NAME DRACO_EXE_LINKER_FLAGS) + draco_test_cxx_flag(FLAG_LIST_VAR_NAMES SAN_CXX_FLAGS FLAG_REQUIRED) + draco_test_exe_linker_flag(FLAG_LIST_VAR_NAME SAN_LINKER_FLAGS) endif() endmacro() diff --git a/contrib/draco/cmake/draco_targets.cmake b/contrib/draco/cmake/draco_targets.cmake index 6dfa6a0c4..0456c4d7b 100644 --- a/contrib/draco/cmake/draco_targets.cmake +++ b/contrib/draco/cmake/draco_targets.cmake @@ -87,6 +87,7 @@ macro(draco_add_executable) endif() add_executable(${exe_NAME} ${exe_SOURCES}) + set_target_properties(${exe_NAME} PROPERTIES VERSION ${DRACO_VERSION}) if(exe_OUTPUT_NAME) set_target_properties(${exe_NAME} PROPERTIES OUTPUT_NAME ${exe_OUTPUT_NAME}) @@ -109,10 +110,11 @@ macro(draco_add_executable) if(exe_LINK_FLAGS OR DRACO_EXE_LINKER_FLAGS) if(${CMAKE_VERSION} VERSION_LESS "3.13") - set(link_flags ${exe_LINK_FLAGS} ${DRACO_EXE_LINKER_FLAGS}) + list(APPEND exe_LINK_FLAGS "${DRACO_EXE_LINKER_FLAGS}") + # LINK_FLAGS is managed as a string. + draco_set_and_stringify(SOURCE "${exe_LINK_FLAGS}" DEST exe_LINK_FLAGS) set_target_properties(${exe_NAME} - PROPERTIES LINK_FLAGS ${exe_LINK_FLAGS} - ${DRACO_EXE_LINKER_FLAGS}) + PROPERTIES LINK_FLAGS "${exe_LINK_FLAGS}") else() target_link_options(${exe_NAME} PRIVATE ${exe_LINK_FLAGS} ${DRACO_EXE_LINKER_FLAGS}) @@ -130,7 +132,7 @@ macro(draco_add_executable) endif() if(BUILD_SHARED_LIBS AND (MSVC OR WIN32)) - target_compile_definitions(${lib_NAME} PRIVATE "DRACO_BUILDING_DLL=0") + target_compile_definitions(${exe_NAME} PRIVATE "DRACO_BUILDING_DLL=0") endif() if(exe_LIB_DEPS) @@ -163,8 +165,8 @@ endmacro() # cmake-format: off # - OUTPUT_NAME: Override output file basename. Target basename defaults to # NAME. OUTPUT_NAME is ignored when BUILD_SHARED_LIBS is enabled and CMake -# is generating a build for which MSVC or WIN32 are true. This is to avoid -# output basename collisions with DLL import libraries. +# is generating a build for which MSVC is true. This is to avoid output +# basename collisions with DLL import libraries. # - TEST: Flag. Presence means treat library as a test. # - DEFINES: List of preprocessor macro definitions. # - INCLUDES: list of include directories for the target. @@ -259,7 +261,7 @@ macro(draco_add_library) endif() if(lib_OUTPUT_NAME) - if(NOT (BUILD_SHARED_LIBS AND (MSVC OR WIN32))) + if(NOT (BUILD_SHARED_LIBS AND MSVC)) set_target_properties(${lib_NAME} PROPERTIES OUTPUT_NAME ${lib_OUTPUT_NAME}) endif() @@ -318,8 +320,12 @@ macro(draco_add_library) set_target_properties(${lib_NAME} PROPERTIES PREFIX "") endif() - if(lib_TYPE STREQUAL SHARED AND NOT MSVC) - set_target_properties(${lib_NAME} PROPERTIES SOVERSION ${DRACO_SOVERSION}) + # VERSION and SOVERSION as necessary + if(NOT lib_TYPE STREQUAL STATIC AND NOT lib_TYPE STREQUAL MODULE) + set_target_properties(${lib_NAME} PROPERTIES VERSION ${DRACO_VERSION}) + if(NOT MSVC) + set_target_properties(${lib_NAME} PROPERTIES SOVERSION ${DRACO_SOVERSION}) + endif() endif() if(BUILD_SHARED_LIBS AND (MSVC OR WIN32)) diff --git a/contrib/draco/src/draco/core/cycle_timer.cc b/contrib/draco/src/draco/core/cycle_timer.cc index 94b4b28b2..58df4df77 100644 --- a/contrib/draco/src/draco/core/cycle_timer.cc +++ b/contrib/draco/src/draco/core/cycle_timer.cc @@ -17,31 +17,31 @@ namespace draco { void DracoTimer::Start() { #ifdef _WIN32 - QueryPerformanceCounter(&tv_start); + QueryPerformanceCounter(&tv_start_); #else - gettimeofday(&tv_start, nullptr); + gettimeofday(&tv_start_, nullptr); #endif } void DracoTimer::Stop() { #ifdef _WIN32 - QueryPerformanceCounter(&tv_end); + QueryPerformanceCounter(&tv_end_); #else - gettimeofday(&tv_end, nullptr); + gettimeofday(&tv_end_, nullptr); #endif } int64_t DracoTimer::GetInMs() { #ifdef _WIN32 LARGE_INTEGER elapsed = {0}; - elapsed.QuadPart = tv_end.QuadPart - tv_start.QuadPart; + elapsed.QuadPart = tv_end_.QuadPart - tv_start_.QuadPart; LARGE_INTEGER frequency = {0}; QueryPerformanceFrequency(&frequency); return elapsed.QuadPart * 1000 / frequency.QuadPart; #else - const int64_t seconds = (tv_end.tv_sec - tv_start.tv_sec) * 1000; - const int64_t milliseconds = (tv_end.tv_usec - tv_start.tv_usec) / 1000; + const int64_t seconds = (tv_end_.tv_sec - tv_start_.tv_sec) * 1000; + const int64_t milliseconds = (tv_end_.tv_usec - tv_start_.tv_usec) / 1000; return seconds + milliseconds; #endif } diff --git a/contrib/draco/src/draco/core/cycle_timer.h b/contrib/draco/src/draco/core/cycle_timer.h index 172f1c2e9..f480cc9d3 100644 --- a/contrib/draco/src/draco/core/cycle_timer.h +++ b/contrib/draco/src/draco/core/cycle_timer.h @@ -20,9 +20,10 @@ #define WIN32_LEAN_AND_MEAN #endif #include -typedef LARGE_INTEGER timeval; +typedef LARGE_INTEGER DracoTimeVal; #else #include +typedef timeval DracoTimeVal; #endif #include @@ -39,8 +40,8 @@ class DracoTimer { int64_t GetInMs(); private: - timeval tv_start; - timeval tv_end; + DracoTimeVal tv_start_; + DracoTimeVal tv_end_; }; typedef DracoTimer CycleTimer; diff --git a/contrib/draco/src/draco/io/parser_utils.cc b/contrib/draco/src/draco/io/parser_utils.cc index 4f95f6f84..12afacff6 100644 --- a/contrib/draco/src/draco/io/parser_utils.cc +++ b/contrib/draco/src/draco/io/parser_utils.cc @@ -18,6 +18,7 @@ #include #include #include +#include namespace draco { namespace parser { @@ -252,7 +253,7 @@ DecoderBuffer ParseLineIntoDecoderBuffer(DecoderBuffer *buffer) { std::string ToLower(const std::string &str) { std::string out; - std::transform(str.begin(), str.end(), std::back_inserter(out), [](unsigned char c){return tolower(c);}); + std::transform(str.begin(), str.end(), std::back_inserter(out), tolower); return out; } diff --git a/contrib/draco/src/draco/io/ply_reader.cc b/contrib/draco/src/draco/io/ply_reader.cc index cb32df225..ea7f2689a 100644 --- a/contrib/draco/src/draco/io/ply_reader.cc +++ b/contrib/draco/src/draco/io/ply_reader.cc @@ -268,14 +268,14 @@ std::vector PlyReader::SplitWords(const std::string &line) { while ((end = line.find_first_of(" \t\n\v\f\r", start)) != std::string::npos) { const std::string word(line.substr(start, end - start)); - if (!std::all_of(word.begin(), word.end(), [](unsigned char c){return isspace(c);})) { + if (!std::all_of(word.begin(), word.end(), isspace)) { output.push_back(word); } start = end + 1; } const std::string last_word(line.substr(start)); - if (!std::all_of(last_word.begin(), last_word.end(), [](unsigned char c){return isspace(c);})) { + if (!std::all_of(last_word.begin(), last_word.end(), isspace)) { output.push_back(last_word); } return output; diff --git a/contrib/draco/src/draco/io/stdio_file_reader.cc b/contrib/draco/src/draco/io/stdio_file_reader.cc index 560c3e9e8..a99c96f8f 100644 --- a/contrib/draco/src/draco/io/stdio_file_reader.cc +++ b/contrib/draco/src/draco/io/stdio_file_reader.cc @@ -87,7 +87,14 @@ size_t StdioFileReader::GetFileSize() { return false; } +#if _FILE_OFFSET_BITS == 64 + const size_t file_size = static_cast(ftello(file_)); +#elif defined _WIN64 + const size_t file_size = static_cast(_ftelli64(file_)); +#else const size_t file_size = static_cast(ftell(file_)); +#endif + rewind(file_); return file_size; diff --git a/contrib/openddlparser/code/OpenDDLExport.cpp b/contrib/openddlparser/code/OpenDDLExport.cpp index 6d6f2458d..d235b553b 100644 --- a/contrib/openddlparser/code/OpenDDLExport.cpp +++ b/contrib/openddlparser/code/OpenDDLExport.cpp @@ -134,10 +134,9 @@ bool OpenDDLExport::writeToStream(const std::string &statement) { } bool OpenDDLExport::writeNode(DDLNode *node, std::string &statement) { - bool success(true); writeNodeHeader(node, statement); if (node->hasProperties()) { - success |= writeProperties(node, statement); + writeProperties(node, statement); } writeLineEnd(statement); diff --git a/contrib/openddlparser/code/OpenDDLParser.cpp b/contrib/openddlparser/code/OpenDDLParser.cpp index 6a9f802ec..0c9e0bd98 100644 --- a/contrib/openddlparser/code/OpenDDLParser.cpp +++ b/contrib/openddlparser/code/OpenDDLParser.cpp @@ -132,6 +132,24 @@ OpenDDLParser::~OpenDDLParser() { clear(); } +void OpenDDLParser::logToStream(FILE *f, LogSeverity severity, const std::string &message) { + if (f) { + const char *tag = "none"; + switch (severity) { + case ddl_debug_msg: tag = "debug"; break; + case ddl_info_msg: tag = "info"; break; + case ddl_warn_msg: tag = "warn"; break; + case ddl_error_msg: tag = "error"; break; + } + fprintf(f, "OpenDDLParser: (%5s) %s\n", tag, message.c_str()); + } +} + +OpenDDLParser::logCallback OpenDDLParser::StdLogCallback (FILE *destination) { + using namespace std::placeholders; + return std::bind(logToStream, destination ? destination : stderr, _1, _2); +} + void OpenDDLParser::setLogCallback(logCallback callback) { // install user-specific log callback; null = no log callback m_logCallback = callback; @@ -171,12 +189,8 @@ size_t OpenDDLParser::getBufferSize() const { void OpenDDLParser::clear() { m_buffer.resize(0); - if (nullptr != m_context) { - delete m_context; - m_context = nullptr; - } - - // DDLNode::releaseNodes(); + delete m_context; + m_context = nullptr; } bool OpenDDLParser::validate() { @@ -506,7 +520,9 @@ char *OpenDDLParser::parseName(char *in, char *end, Name **name) { in = parseIdentifier(in, end, &id); if (id) { currentName = new Name(ntype, id); - *name = currentName; + if (currentName) { + *name = currentName; + } } return in; diff --git a/contrib/openddlparser/code/Value.cpp b/contrib/openddlparser/code/Value.cpp index 708a6878f..5a8aa39be 100644 --- a/contrib/openddlparser/code/Value.cpp +++ b/contrib/openddlparser/code/Value.cpp @@ -113,13 +113,14 @@ Value::~Value() { if (m_data != nullptr) { if (m_type == ValueType::ddl_ref) { Reference *tmp = (Reference *)m_data; - if (tmp != nullptr) + if (tmp != nullptr) { delete tmp; - } else + } + } else { delete[] m_data; + } } - if (m_next != nullptr) - delete m_next; + delete m_next; } void Value::setBool(bool value) { diff --git a/contrib/openddlparser/include/openddlparser/OpenDDLParser.h b/contrib/openddlparser/include/openddlparser/OpenDDLParser.h index 5794add90..3fbb4b6af 100644 --- a/contrib/openddlparser/include/openddlparser/OpenDDLParser.h +++ b/contrib/openddlparser/include/openddlparser/OpenDDLParser.h @@ -29,6 +29,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +#include BEGIN_ODDLPARSER_NS @@ -97,8 +98,8 @@ DLL_ODDLPARSER_EXPORT const char *getTypeToken(Value::ValueType type); //------------------------------------------------------------------------------------------------- class DLL_ODDLPARSER_EXPORT OpenDDLParser { public: - /// @brief The log callback function pointer. - typedef void (*logCallback)(LogSeverity severity, const std::string &msg); + /// @brief The log callback function. + typedef std::function logCallback; public: /// @brief The default class constructor. @@ -120,6 +121,11 @@ public: /// @return The current log callback. logCallback getLogCallback() const; + /// @brief A default log callback that writes to a FILE. + /// @param destination [in] Output stream. NULL will use stderr. + /// @return A callback that you can pass to setLogCallback. + static logCallback StdLogCallback(FILE *destination = nullptr); + /// @brief Assigns a new buffer to parse. /// @param buffer [in] The buffer /// @param len [in] Size of the buffer @@ -192,6 +198,9 @@ private: typedef std::vector DDLNodeStack; DDLNodeStack m_stack; Context *m_context; + + /// @brief Callback for StdLogCallback(). Not meant to be called directly. + static void logToStream (FILE *, LogSeverity, const std::string &); }; END_ODDLPARSER_NS diff --git a/contrib/poly2tri/poly2tri/sweep/sweep.cc b/contrib/poly2tri/poly2tri/sweep/sweep.cc index 23aeb6b57..8e3d794c0 100644 --- a/contrib/poly2tri/poly2tri/sweep/sweep.cc +++ b/contrib/poly2tri/poly2tri/sweep/sweep.cc @@ -129,7 +129,7 @@ void Sweep::EdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* triangl EdgeEvent( tcx, ep, *p1, triangle, *p1 ); } else { // ASSIMP_CHANGE (aramis_acg) - std::runtime_error("EdgeEvent - collinear points not supported"); + throw std::runtime_error("EdgeEvent - collinear points not supported"); } return; } diff --git a/code/Pbrt/stb_image.h b/contrib/stb/stb_image.h similarity index 100% rename from code/Pbrt/stb_image.h rename to contrib/stb/stb_image.h diff --git a/contrib/stb_image/stb_image.h b/contrib/stb_image/stb_image.h deleted file mode 100644 index d9c21bc81..000000000 --- a/contrib/stb_image/stb_image.h +++ /dev/null @@ -1,7462 +0,0 @@ -/* stb_image - v2.19 - public domain image loader - http://nothings.org/stb - no warranty implied; use at your own risk - - Do this: - #define STB_IMAGE_IMPLEMENTATION - before you include this file in *one* C or C++ file to create the implementation. - - // i.e. it should look like this: - #include ... - #include ... - #include ... - #define STB_IMAGE_IMPLEMENTATION - #include "stb_image.h" - - You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. - And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free - - - QUICK NOTES: - Primarily of interest to game developers and other people who can - avoid problematic images and only need the trivial interface - - JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) - PNG 1/2/4/8/16-bit-per-channel - - TGA (not sure what subset, if a subset) - BMP non-1bpp, non-RLE - PSD (composited view only, no extra channels, 8/16 bit-per-channel) - - GIF (*comp always reports as 4-channel) - HDR (radiance rgbE format) - PIC (Softimage PIC) - PNM (PPM and PGM binary only) - - Animated GIF still needs a proper API, but here's one way to do it: - http://gist.github.com/urraka/685d9a6340b26b830d49 - - - decode from memory or through FILE (define STBI_NO_STDIO to remove code) - - decode from arbitrary I/O callbacks - - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) - - Full documentation under "DOCUMENTATION" below. - - -LICENSE - - See end of file for license information. - -RECENT REVISION HISTORY: - - 2.19 (2018-02-11) fix warning - 2.18 (2018-01-30) fix warnings - 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings - 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes - 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC - 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs - 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes - 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes - 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 - RGB-format JPEG; remove white matting in PSD; - allocate large structures on the stack; - correct channel count for PNG & BMP - 2.10 (2016-01-22) avoid warning introduced in 2.09 - 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED - - See end of file for full revision history. - - - ============================ Contributors ========================= - - Image formats Extensions, features - Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) - Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) - Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) - Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) - Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) - Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) - Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) - github:urraka (animated gif) Junggon Kim (PNM comments) - Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) - socks-the-fox (16-bit PNG) - Jeremy Sawicki (handle all ImageNet JPGs) - Optimizations & bugfixes Mikhail Morozov (1-bit BMP) - Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) - Arseny Kapoulkine - John-Mark Allen - - Bug & warning fixes - Marc LeBlanc David Woo Guillaume George Martins Mozeiko - Christpher Lloyd Jerry Jansson Joseph Thomson Phil Jordan - Dave Moore Roy Eltham Hayaki Saito Nathan Reed - Won Chun Luke Graham Johan Duparc Nick Verigakis - the Horde3D community Thomas Ruf Ronny Chevalier github:rlyeh - Janez Zemva John Bartholomew Michal Cichon github:romigrou - Jonathan Blow Ken Hamada Tero Hanninen github:svdijk - Laurent Gomila Cort Stratton Sergio Gonzalez github:snagar - Aruelien Pocheville Thibault Reuille Cass Everitt github:Zelex - Ryamond Barbiero Paul Du Bois Engin Manap github:grim210 - Aldo Culquicondor Philipp Wiesemann Dale Weiler github:sammyhw - Oriol Ferrer Mesia Josh Tobin Matthew Gregan github:phprus - Julian Raschke Gregory Mullen Baldur Karlsson github:poppolopoppo - Christian Floisand Kevin Schmidt github:darealshinji - Blazej Dariusz Roszkowski github:Michaelangel007 -*/ - -#ifndef STBI_INCLUDE_STB_IMAGE_H -#define STBI_INCLUDE_STB_IMAGE_H - -// DOCUMENTATION -// -// Limitations: -// - no 12-bit-per-channel JPEG -// - no JPEGs with arithmetic coding -// - GIF always returns *comp=4 -// -// Basic usage (see HDR discussion below for HDR usage): -// int x,y,n; -// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); -// // ... process data if not NULL ... -// // ... x = width, y = height, n = # 8-bit components per pixel ... -// // ... replace '0' with '1'..'4' to force that many components per pixel -// // ... but 'n' will always be the number that it would have been if you said 0 -// stbi_image_free(data) -// -// Standard parameters: -// int *x -- outputs image width in pixels -// int *y -- outputs image height in pixels -// int *channels_in_file -- outputs # of image components in image file -// int desired_channels -- if non-zero, # of image components requested in result -// -// The return value from an image loader is an 'unsigned char *' which points -// to the pixel data, or NULL on an allocation failure or if the image is -// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, -// with each pixel consisting of N interleaved 8-bit components; the first -// pixel pointed to is top-left-most in the image. There is no padding between -// image scanlines or between pixels, regardless of format. The number of -// components N is 'desired_channels' if desired_channels is non-zero, or -// *channels_in_file otherwise. If desired_channels is non-zero, -// *channels_in_file has the number of components that _would_ have been -// output otherwise. E.g. if you set desired_channels to 4, you will always -// get RGBA output, but you can check *channels_in_file to see if it's trivially -// opaque because e.g. there were only 3 channels in the source image. -// -// An output image with N components has the following components interleaved -// in this order in each pixel: -// -// N=#comp components -// 1 grey -// 2 grey, alpha -// 3 red, green, blue -// 4 red, green, blue, alpha -// -// If image loading fails for any reason, the return value will be NULL, -// and *x, *y, *channels_in_file will be unchanged. The function -// stbi_failure_reason() can be queried for an extremely brief, end-user -// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS -// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly -// more user-friendly ones. -// -// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. -// -// =========================================================================== -// -// Philosophy -// -// stb libraries are designed with the following priorities: -// -// 1. easy to use -// 2. easy to maintain -// 3. good performance -// -// Sometimes I let "good performance" creep up in priority over "easy to maintain", -// and for best performance I may provide less-easy-to-use APIs that give higher -// performance, in addition to the easy to use ones. Nevertheless, it's important -// to keep in mind that from the standpoint of you, a client of this library, -// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. -// -// Some secondary priorities arise directly from the first two, some of which -// make more explicit reasons why performance can't be emphasized. -// -// - Portable ("ease of use") -// - Small source code footprint ("easy to maintain") -// - No dependencies ("ease of use") -// -// =========================================================================== -// -// I/O callbacks -// -// I/O callbacks allow you to read from arbitrary sources, like packaged -// files or some other source. Data read from callbacks are processed -// through a small internal buffer (currently 128 bytes) to try to reduce -// overhead. -// -// The three functions you must define are "read" (reads some bytes of data), -// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). -// -// =========================================================================== -// -// SIMD support -// -// The JPEG decoder will try to automatically use SIMD kernels on x86 when -// supported by the compiler. For ARM Neon support, you must explicitly -// request it. -// -// (The old do-it-yourself SIMD API is no longer supported in the current -// code.) -// -// On x86, SSE2 will automatically be used when available based on a run-time -// test; if not, the generic C versions are used as a fall-back. On ARM targets, -// the typical path is to have separate builds for NEON and non-NEON devices -// (at least this is true for iOS and Android). Therefore, the NEON support is -// toggled by a build flag: define STBI_NEON to get NEON loops. -// -// If for some reason you do not want to use any of SIMD code, or if -// you have issues compiling it, you can disable it entirely by -// defining STBI_NO_SIMD. -// -// =========================================================================== -// -// HDR image support (disable by defining STBI_NO_HDR) -// -// stb_image now supports loading HDR images in general, and currently -// the Radiance .HDR file format, although the support is provided -// generically. You can still load any file through the existing interface; -// if you attempt to load an HDR file, it will be automatically remapped to -// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; -// both of these constants can be reconfigured through this interface: -// -// stbi_hdr_to_ldr_gamma(2.2f); -// stbi_hdr_to_ldr_scale(1.0f); -// -// (note, do not use _inverse_ constants; stbi_image will invert them -// appropriately). -// -// Additionally, there is a new, parallel interface for loading files as -// (linear) floats to preserve the full dynamic range: -// -// float *data = stbi_loadf(filename, &x, &y, &n, 0); -// -// If you load LDR images through this interface, those images will -// be promoted to floating point values, run through the inverse of -// constants corresponding to the above: -// -// stbi_ldr_to_hdr_scale(1.0f); -// stbi_ldr_to_hdr_gamma(2.2f); -// -// Finally, given a filename (or an open file or memory block--see header -// file for details) containing image data, you can query for the "most -// appropriate" interface to use (that is, whether the image is HDR or -// not), using: -// -// stbi_is_hdr(char *filename); -// -// =========================================================================== -// -// iPhone PNG support: -// -// By default we convert iphone-formatted PNGs back to RGB, even though -// they are internally encoded differently. You can disable this conversion -// by by calling stbi_convert_iphone_png_to_rgb(0), in which case -// you will always just get the native iphone "format" through (which -// is BGR stored in RGB). -// -// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per -// pixel to remove any premultiplied alpha *only* if the image file explicitly -// says there's premultiplied data (currently only happens in iPhone images, -// and only if iPhone convert-to-rgb processing is on). -// -// =========================================================================== -// -// ADDITIONAL CONFIGURATION -// -// - You can suppress implementation of any of the decoders to reduce -// your code footprint by #defining one or more of the following -// symbols before creating the implementation. -// -// STBI_NO_JPEG -// STBI_NO_PNG -// STBI_NO_BMP -// STBI_NO_PSD -// STBI_NO_TGA -// STBI_NO_GIF -// STBI_NO_HDR -// STBI_NO_PIC -// STBI_NO_PNM (.ppm and .pgm) -// -// - You can request *only* certain decoders and suppress all other ones -// (this will be more forward-compatible, as addition of new decoders -// doesn't require you to disable them explicitly): -// -// STBI_ONLY_JPEG -// STBI_ONLY_PNG -// STBI_ONLY_BMP -// STBI_ONLY_PSD -// STBI_ONLY_TGA -// STBI_ONLY_GIF -// STBI_ONLY_HDR -// STBI_ONLY_PIC -// STBI_ONLY_PNM (.ppm and .pgm) -// -// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still -// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB -// - - -#ifndef STBI_NO_STDIO -#include -#endif // STBI_NO_STDIO - -#define STBI_VERSION 1 - -enum -{ - STBI_default = 0, // only used for desired_channels - - STBI_grey = 1, - STBI_grey_alpha = 2, - STBI_rgb = 3, - STBI_rgb_alpha = 4 -}; - -typedef unsigned char stbi_uc; -typedef unsigned short stbi_us; - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef STB_IMAGE_STATIC -#define STBIDEF static -#else -#define STBIDEF extern -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// PRIMARY API - works on images of any type -// - -// -// load image by filename, open file, or memory buffer -// - -typedef struct -{ - int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read - void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative - int (*eof) (void *user); // returns nonzero if we are at end of file/data -} stbi_io_callbacks; - -//////////////////////////////////// -// -// 8-bits-per-channel interface -// - -STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); -#ifndef STBI_NO_GIF -STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); -#endif - - -#ifndef STBI_NO_STDIO -STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); -// for stbi_load_from_file, file pointer is left pointing immediately after image -#endif - -//////////////////////////////////// -// -// 16-bits-per-channel interface -// - -STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); - -#ifndef STBI_NO_STDIO -STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); -#endif - -//////////////////////////////////// -// -// float-per-channel interface -// -#ifndef STBI_NO_LINEAR - STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); - STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); - - #ifndef STBI_NO_STDIO - STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); - STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); - #endif -#endif - -#ifndef STBI_NO_HDR - STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); - STBIDEF void stbi_hdr_to_ldr_scale(float scale); -#endif // STBI_NO_HDR - -#ifndef STBI_NO_LINEAR - STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); - STBIDEF void stbi_ldr_to_hdr_scale(float scale); -#endif // STBI_NO_LINEAR - -// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR -STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); -STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); -#ifndef STBI_NO_STDIO -STBIDEF int stbi_is_hdr (char const *filename); -STBIDEF int stbi_is_hdr_from_file(FILE *f); -#endif // STBI_NO_STDIO - - -// get a VERY brief reason for failure -// NOT THREADSAFE -STBIDEF const char *stbi_failure_reason (void); - -// free the loaded image -- this is just free() -STBIDEF void stbi_image_free (void *retval_from_stbi_load); - -// get image dimensions & components without fully decoding -STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); -STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); -STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); -STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); -STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); -STBIDEF int stbi_is_16_bit (char const *filename); -STBIDEF int stbi_is_16_bit_from_file(FILE *f); -#endif - - - -// for image formats that explicitly notate that they have premultiplied alpha, -// we just return the colors as stored in the file. set this flag to force -// unpremultiplication. results are undefined if the unpremultiply overflow. -STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); - -// indicate whether we should process iphone images back to canonical format, -// or just pass them through "as-is" -STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); - -// flip the image vertically, so the first pixel in the output array is the bottom left -STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); - -// ZLIB client - used by PNG, available for other purposes - -STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); -STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); -STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); -STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); - -STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); -STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); - - -#ifdef __cplusplus -} -#endif - -// -// -//// end header file ///////////////////////////////////////////////////// -#endif // STBI_INCLUDE_STB_IMAGE_H - -#ifdef STB_IMAGE_IMPLEMENTATION - -#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ - || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ - || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ - || defined(STBI_ONLY_ZLIB) - #ifndef STBI_ONLY_JPEG - #define STBI_NO_JPEG - #endif - #ifndef STBI_ONLY_PNG - #define STBI_NO_PNG - #endif - #ifndef STBI_ONLY_BMP - #define STBI_NO_BMP - #endif - #ifndef STBI_ONLY_PSD - #define STBI_NO_PSD - #endif - #ifndef STBI_ONLY_TGA - #define STBI_NO_TGA - #endif - #ifndef STBI_ONLY_GIF - #define STBI_NO_GIF - #endif - #ifndef STBI_ONLY_HDR - #define STBI_NO_HDR - #endif - #ifndef STBI_ONLY_PIC - #define STBI_NO_PIC - #endif - #ifndef STBI_ONLY_PNM - #define STBI_NO_PNM - #endif -#endif - -#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) -#define STBI_NO_ZLIB -#endif - - -#include -#include // ptrdiff_t on osx -#include -#include -#include - -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) -#include // ldexp, pow -#endif - -#ifndef STBI_NO_STDIO -#include -#endif - -#ifndef STBI_ASSERT -#include -#define STBI_ASSERT(x) assert(x) -#endif - - -#ifndef _MSC_VER - #ifdef __cplusplus - #define stbi_inline inline - #else - #define stbi_inline - #endif -#else - #define stbi_inline __forceinline -#endif - - -#ifdef _MSC_VER -typedef unsigned short stbi__uint16; -typedef signed short stbi__int16; -typedef unsigned int stbi__uint32; -typedef signed int stbi__int32; -#else -#include -typedef uint16_t stbi__uint16; -typedef int16_t stbi__int16; -typedef uint32_t stbi__uint32; -typedef int32_t stbi__int32; -#endif - -// should produce compiler error if size is wrong -typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; - -#ifdef _MSC_VER -#define STBI_NOTUSED(v) (void)(v) -#else -#define STBI_NOTUSED(v) (void)sizeof(v) -#endif - -#ifdef _MSC_VER -#define STBI_HAS_LROTL -#endif - -#ifdef STBI_HAS_LROTL - #define stbi_lrot(x,y) _lrotl(x,y) -#else - #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) -#endif - -#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) -// ok -#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) -// ok -#else -#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." -#endif - -#ifndef STBI_MALLOC -#define STBI_MALLOC(sz) malloc(sz) -#define STBI_REALLOC(p,newsz) realloc(p,newsz) -#define STBI_FREE(p) free(p) -#endif - -#ifndef STBI_REALLOC_SIZED -#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) -#endif - -// x86/x64 detection -#if defined(__x86_64__) || defined(_M_X64) -#define STBI__X64_TARGET -#elif defined(__i386) || defined(_M_IX86) -#define STBI__X86_TARGET -#endif - -#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) -// gcc doesn't support sse2 intrinsics unless you compile with -msse2, -// which in turn means it gets to use SSE2 everywhere. This is unfortunate, -// but previous attempts to provide the SSE2 functions with runtime -// detection caused numerous issues. The way architecture extensions are -// exposed in GCC/Clang is, sadly, not really suited for one-file libs. -// New behavior: if compiled with -msse2, we use SSE2 without any -// detection; if not, we don't use it at all. -#define STBI_NO_SIMD -#endif - -#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) -// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET -// -// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the -// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. -// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not -// simultaneously enabling "-mstackrealign". -// -// See https://github.com/nothings/stb/issues/81 for more information. -// -// So default to no SSE2 on 32-bit MinGW. If you've read this far and added -// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. -#define STBI_NO_SIMD -#endif - -#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) -#define STBI_SSE2 -#include - -#ifdef _MSC_VER - -#if _MSC_VER >= 1400 // not VC6 -#include // __cpuid -static int stbi__cpuid3(void) -{ - int info[4]; - __cpuid(info,1); - return info[3]; -} -#else -static int stbi__cpuid3(void) -{ - int res; - __asm { - mov eax,1 - cpuid - mov res,edx - } - return res; -} -#endif - -#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name - -static int stbi__sse2_available(void) -{ - int info3 = stbi__cpuid3(); - return ((info3 >> 26) & 1) != 0; -} -#else // assume GCC-style if not VC++ -#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) - -static int stbi__sse2_available(void) -{ - // If we're even attempting to compile this on GCC/Clang, that means - // -msse2 is on, which means the compiler is allowed to use SSE2 - // instructions at will, and so are we. - return 1; -} -#endif -#endif - -// ARM NEON -#if defined(STBI_NO_SIMD) && defined(STBI_NEON) -#undef STBI_NEON -#endif - -#ifdef STBI_NEON -#include -// assume GCC or Clang on ARM targets -#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) -#endif - -#ifndef STBI_SIMD_ALIGN -#define STBI_SIMD_ALIGN(type, name) type name -#endif - -/////////////////////////////////////////////// -// -// stbi__context struct and start_xxx functions - -// stbi__context structure is our basic context used by all images, so it -// contains all the IO context, plus some basic image information -typedef struct -{ - stbi__uint32 img_x, img_y; - int img_n, img_out_n; - - stbi_io_callbacks io; - void *io_user_data; - - int read_from_callbacks; - int buflen; - stbi_uc buffer_start[128]; - - stbi_uc *img_buffer, *img_buffer_end; - stbi_uc *img_buffer_original, *img_buffer_original_end; -} stbi__context; - - -static void stbi__refill_buffer(stbi__context *s); - -// initialize a memory-decode context -static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) -{ - s->io.read = NULL; - s->read_from_callbacks = 0; - s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; - s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; -} - -// initialize a callback-based context -static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) -{ - s->io = *c; - s->io_user_data = user; - s->buflen = sizeof(s->buffer_start); - s->read_from_callbacks = 1; - s->img_buffer_original = s->buffer_start; - stbi__refill_buffer(s); - s->img_buffer_original_end = s->img_buffer_end; -} - -#ifndef STBI_NO_STDIO - -static int stbi__stdio_read(void *user, char *data, int size) -{ - return (int) fread(data,1,size,(FILE*) user); -} - -static void stbi__stdio_skip(void *user, int n) -{ - fseek((FILE*) user, n, SEEK_CUR); -} - -static int stbi__stdio_eof(void *user) -{ - return feof((FILE*) user); -} - -static stbi_io_callbacks stbi__stdio_callbacks = -{ - stbi__stdio_read, - stbi__stdio_skip, - stbi__stdio_eof, -}; - -static void stbi__start_file(stbi__context *s, FILE *f) -{ - stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); -} - -//static void stop_file(stbi__context *s) { } - -#endif // !STBI_NO_STDIO - -static void stbi__rewind(stbi__context *s) -{ - // conceptually rewind SHOULD rewind to the beginning of the stream, - // but we just rewind to the beginning of the initial buffer, because - // we only use it after doing 'test', which only ever looks at at most 92 bytes - s->img_buffer = s->img_buffer_original; - s->img_buffer_end = s->img_buffer_original_end; -} - -enum -{ - STBI_ORDER_RGB, - STBI_ORDER_BGR -}; - -typedef struct -{ - int bits_per_channel; - int num_channels; - int channel_order; -} stbi__result_info; - -#ifndef STBI_NO_JPEG -static int stbi__jpeg_test(stbi__context *s); -static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PNG -static int stbi__png_test(stbi__context *s); -static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); -static int stbi__png_is16(stbi__context *s); -#endif - -#ifndef STBI_NO_BMP -static int stbi__bmp_test(stbi__context *s); -static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_TGA -static int stbi__tga_test(stbi__context *s); -static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PSD -static int stbi__psd_test(stbi__context *s); -static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); -static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); -static int stbi__psd_is16(stbi__context *s); -#endif - -#ifndef STBI_NO_HDR -static int stbi__hdr_test(stbi__context *s); -static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PIC -static int stbi__pic_test(stbi__context *s); -static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_GIF -static int stbi__gif_test(stbi__context *s); -static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); -static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PNM -static int stbi__pnm_test(stbi__context *s); -static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -// this is not threadsafe -static const char *stbi__g_failure_reason; - -STBIDEF const char *stbi_failure_reason(void) -{ - return stbi__g_failure_reason; -} - -static int stbi__err(const char *str) -{ - stbi__g_failure_reason = str; - return 0; -} - -static void *stbi__malloc(size_t size) -{ - return STBI_MALLOC(size); -} - -// stb_image uses ints pervasively, including for offset calculations. -// therefore the largest decoded image size we can support with the -// current code, even on 64-bit targets, is INT_MAX. this is not a -// significant limitation for the intended use case. -// -// we do, however, need to make sure our size calculations don't -// overflow. hence a few helper functions for size calculations that -// multiply integers together, making sure that they're non-negative -// and no overflow occurs. - -// return 1 if the sum is valid, 0 on overflow. -// negative terms are considered invalid. -static int stbi__addsizes_valid(int a, int b) -{ - if (b < 0) return 0; - // now 0 <= b <= INT_MAX, hence also - // 0 <= INT_MAX - b <= INTMAX. - // And "a + b <= INT_MAX" (which might overflow) is the - // same as a <= INT_MAX - b (no overflow) - return a <= INT_MAX - b; -} - -// returns 1 if the product is valid, 0 on overflow. -// negative factors are considered invalid. -static int stbi__mul2sizes_valid(int a, int b) -{ - if (a < 0 || b < 0) return 0; - if (b == 0) return 1; // mul-by-0 is always safe - // portable way to check for no overflows in a*b - return a <= INT_MAX/b; -} - -// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow -static int stbi__mad2sizes_valid(int a, int b, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); -} - -// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow -static int stbi__mad3sizes_valid(int a, int b, int c, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && - stbi__addsizes_valid(a*b*c, add); -} - -// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) -static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && - stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); -} -#endif - -// mallocs with size overflow checking -static void *stbi__malloc_mad2(int a, int b, int add) -{ - if (!stbi__mad2sizes_valid(a, b, add)) return NULL; - return stbi__malloc(a*b + add); -} - -static void *stbi__malloc_mad3(int a, int b, int c, int add) -{ - if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; - return stbi__malloc(a*b*c + add); -} - -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) -static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) -{ - if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; - return stbi__malloc(a*b*c*d + add); -} -#endif - -// stbi__err - error -// stbi__errpf - error returning pointer to float -// stbi__errpuc - error returning pointer to unsigned char - -#ifdef STBI_NO_FAILURE_STRINGS - #define stbi__err(x,y) 0 -#elif defined(STBI_FAILURE_USERMSG) - #define stbi__err(x,y) stbi__err(y) -#else - #define stbi__err(x,y) stbi__err(x) -#endif - -#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) -#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) - -STBIDEF void stbi_image_free(void *retval_from_stbi_load) -{ - STBI_FREE(retval_from_stbi_load); -} - -#ifndef STBI_NO_LINEAR -static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); -#endif - -#ifndef STBI_NO_HDR -static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); -#endif - -static int stbi__vertically_flip_on_load = 0; - -STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) -{ - stbi__vertically_flip_on_load = flag_true_if_should_flip; -} - -static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) -{ - memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields - ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed - ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order - ri->num_channels = 0; - - #ifndef STBI_NO_JPEG - if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_PNG - if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_BMP - if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_GIF - if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_PSD - if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); - #endif - #ifndef STBI_NO_PIC - if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_PNM - if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); - #endif - - #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) { - float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); - return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); - } - #endif - - #ifndef STBI_NO_TGA - // test tga last because it's a crappy test! - if (stbi__tga_test(s)) - return stbi__tga_load(s,x,y,comp,req_comp, ri); - #endif - - return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); -} - -static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) -{ - int i; - int img_len = w * h * channels; - stbi_uc *reduced; - - reduced = (stbi_uc *) stbi__malloc(img_len); - if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); - - for (i = 0; i < img_len; ++i) - reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling - - STBI_FREE(orig); - return reduced; -} - -static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) -{ - int i; - int img_len = w * h * channels; - stbi__uint16 *enlarged; - - enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); - if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); - - for (i = 0; i < img_len; ++i) - enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff - - STBI_FREE(orig); - return enlarged; -} - -static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) -{ - int row; - size_t bytes_per_row = (size_t)w * bytes_per_pixel; - stbi_uc temp[2048]; - stbi_uc *bytes = (stbi_uc *)image; - - for (row = 0; row < (h>>1); row++) { - stbi_uc *row0 = bytes + row*bytes_per_row; - stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; - // swap row0 with row1 - size_t bytes_left = bytes_per_row; - while (bytes_left) { - size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); - memcpy(temp, row0, bytes_copy); - memcpy(row0, row1, bytes_copy); - memcpy(row1, temp, bytes_copy); - row0 += bytes_copy; - row1 += bytes_copy; - bytes_left -= bytes_copy; - } - } -} - -static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) -{ - int slice; - int slice_size = w * h * bytes_per_pixel; - - stbi_uc *bytes = (stbi_uc *)image; - for (slice = 0; slice < z; ++slice) { - stbi__vertical_flip(bytes, w, h, bytes_per_pixel); - bytes += slice_size; - } -} - -static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi__result_info ri; - void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); - - if (result == NULL) - return NULL; - - if (ri.bits_per_channel != 8) { - STBI_ASSERT(ri.bits_per_channel == 16); - result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); - ri.bits_per_channel = 8; - } - - // @TODO: move stbi__convert_format to here - - if (stbi__vertically_flip_on_load) { - int channels = req_comp ? req_comp : *comp; - stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); - } - - return (unsigned char *) result; -} - -static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi__result_info ri; - void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); - - if (result == NULL) - return NULL; - - if (ri.bits_per_channel != 16) { - STBI_ASSERT(ri.bits_per_channel == 8); - result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); - ri.bits_per_channel = 16; - } - - // @TODO: move stbi__convert_format16 to here - // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision - - if (stbi__vertically_flip_on_load) { - int channels = req_comp ? req_comp : *comp; - stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); - } - - return (stbi__uint16 *) result; -} - -#if !defined(STBI_NO_HDR) || !defined(STBI_NO_LINEAR) -static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) -{ - if (stbi__vertically_flip_on_load && result != NULL) { - int channels = req_comp ? req_comp : *comp; - stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); - } -} -#endif - -#ifndef STBI_NO_STDIO - -static FILE *stbi__fopen(char const *filename, char const *mode) -{ - FILE *f; -#if defined(_MSC_VER) && _MSC_VER >= 1400 - if (0 != fopen_s(&f, filename, mode)) - f=0; -#else - f = fopen(filename, mode); -#endif - return f; -} - - -STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - unsigned char *result; - if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); - result = stbi_load_from_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} - -STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *result; - stbi__context s; - stbi__start_file(&s,f); - result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); - if (result) { - // need to 'unget' all the characters in the IO buffer - fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); - } - return result; -} - -STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__uint16 *result; - stbi__context s; - stbi__start_file(&s,f); - result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); - if (result) { - // need to 'unget' all the characters in the IO buffer - fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); - } - return result; -} - -STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - stbi__uint16 *result; - if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); - result = stbi_load_from_file_16(f,x,y,comp,req_comp); - fclose(f); - return result; -} - - -#endif //!STBI_NO_STDIO - -STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); -} - -STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); - return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); -} - -STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); -} - -STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); -} - -#ifndef STBI_NO_GIF -STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) -{ - unsigned char *result; - stbi__context s; - stbi__start_mem(&s,buffer,len); - - result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); - if (stbi__vertically_flip_on_load) { - stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); - } - - return result; -} -#endif - -#ifndef STBI_NO_LINEAR -static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *data; - #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) { - stbi__result_info ri; - float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); - if (hdr_data) - stbi__float_postprocess(hdr_data,x,y,comp,req_comp); - return hdr_data; - } - #endif - data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); - if (data) - return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); - return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); -} - -STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} - -STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} - -#ifndef STBI_NO_STDIO -STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - float *result; - FILE *f = stbi__fopen(filename, "rb"); - if (!f) return stbi__errpf("can't fopen", "Unable to open file"); - result = stbi_loadf_from_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} - -STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_file(&s,f); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} -#endif // !STBI_NO_STDIO - -#endif // !STBI_NO_LINEAR - -// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is -// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always -// reports false! - -STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__hdr_test(&s); - #else - STBI_NOTUSED(buffer); - STBI_NOTUSED(len); - return 0; - #endif -} - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_is_hdr (char const *filename) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result=0; - if (f) { - result = stbi_is_hdr_from_file(f); - fclose(f); - } - return result; -} - -STBIDEF int stbi_is_hdr_from_file(FILE *f) -{ - #ifndef STBI_NO_HDR - long pos = ftell(f); - int res; - stbi__context s; - stbi__start_file(&s,f); - res = stbi__hdr_test(&s); - fseek(f, pos, SEEK_SET); - return res; - #else - STBI_NOTUSED(f); - return 0; - #endif -} -#endif // !STBI_NO_STDIO - -STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__hdr_test(&s); - #else - STBI_NOTUSED(clbk); - STBI_NOTUSED(user); - return 0; - #endif -} - -#ifndef STBI_NO_LINEAR -static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; - -STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } -STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } -#endif - -static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; - -STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } -STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } - - -////////////////////////////////////////////////////////////////////////////// -// -// Common code used by all image loaders -// - -enum -{ - STBI__SCAN_load=0, - STBI__SCAN_type, - STBI__SCAN_header -}; - -static void stbi__refill_buffer(stbi__context *s) -{ - int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); - if (n == 0) { - // at end of file, treat same as if from memory, but need to handle case - // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file - s->read_from_callbacks = 0; - s->img_buffer = s->buffer_start; - s->img_buffer_end = s->buffer_start+1; - *s->img_buffer = 0; - } else { - s->img_buffer = s->buffer_start; - s->img_buffer_end = s->buffer_start + n; - } -} - -stbi_inline static stbi_uc stbi__get8(stbi__context *s) -{ - if (s->img_buffer < s->img_buffer_end) - return *s->img_buffer++; - if (s->read_from_callbacks) { - stbi__refill_buffer(s); - return *s->img_buffer++; - } - return 0; -} - -stbi_inline static int stbi__at_eof(stbi__context *s) -{ - if (s->io.read) { - if (!(s->io.eof)(s->io_user_data)) return 0; - // if feof() is true, check if buffer = end - // special case: we've only got the special 0 character at the end - if (s->read_from_callbacks == 0) return 1; - } - - return s->img_buffer >= s->img_buffer_end; -} - -static void stbi__skip(stbi__context *s, int n) -{ - if (n < 0) { - s->img_buffer = s->img_buffer_end; - return; - } - if (s->io.read) { - int blen = (int) (s->img_buffer_end - s->img_buffer); - if (blen < n) { - s->img_buffer = s->img_buffer_end; - (s->io.skip)(s->io_user_data, n - blen); - return; - } - } - s->img_buffer += n; -} - -static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) -{ - if (s->io.read) { - int blen = (int) (s->img_buffer_end - s->img_buffer); - if (blen < n) { - int res, count; - - memcpy(buffer, s->img_buffer, blen); - - count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); - res = (count == (n-blen)); - s->img_buffer = s->img_buffer_end; - return res; - } - } - - if (s->img_buffer+n <= s->img_buffer_end) { - memcpy(buffer, s->img_buffer, n); - s->img_buffer += n; - return 1; - } else - return 0; -} - -static int stbi__get16be(stbi__context *s) -{ - int z = stbi__get8(s); - return (z << 8) + stbi__get8(s); -} - -static stbi__uint32 stbi__get32be(stbi__context *s) -{ - stbi__uint32 z = stbi__get16be(s); - return (z << 16) + stbi__get16be(s); -} - -#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) -// nothing -#else -static int stbi__get16le(stbi__context *s) -{ - int z = stbi__get8(s); - return z + (stbi__get8(s) << 8); -} -#endif - -#ifndef STBI_NO_BMP -static stbi__uint32 stbi__get32le(stbi__context *s) -{ - stbi__uint32 z = stbi__get16le(s); - return z + (stbi__get16le(s) << 16); -} -#endif - -#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings - - -////////////////////////////////////////////////////////////////////////////// -// -// generic converter from built-in img_n to req_comp -// individual types do this automatically as much as possible (e.g. jpeg -// does all cases internally since it needs to colorspace convert anyway, -// and it never has alpha, so very few cases ). png can automatically -// interleave an alpha=255 channel, but falls back to this for other cases -// -// assume data buffer is malloced, so malloc a new one and free that one -// only failure mode is malloc failing - -static stbi_uc stbi__compute_y(int r, int g, int b) -{ - return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); -} - -static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) -{ - int i,j; - unsigned char *good; - - if (req_comp == img_n) return data; - STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - - good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); - if (good == NULL) { - STBI_FREE(data); - return stbi__errpuc("outofmem", "Out of memory"); - } - - for (j=0; j < (int) y; ++j) { - unsigned char *src = data + j * x * img_n ; - unsigned char *dest = good + j * x * req_comp; - - #define STBI__COMBO(a,b) ((a)*8+(b)) - #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) - // convert source image with img_n components to one with req_comp components; - // avoid switch per pixel, so use switch per scanline and massive macros - switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1,2) { dest[0]=src[0], dest[1]=255; } break; - STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; } break; - STBI__CASE(2,1) { dest[0]=src[0]; } break; - STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; - STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; } break; - STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; - STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; } break; - STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; - STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; } break; - STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; - default: STBI_ASSERT(0); - } - #undef STBI__CASE - } - - STBI_FREE(data); - return good; -} - -static stbi__uint16 stbi__compute_y_16(int r, int g, int b) -{ - return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); -} - -static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) -{ - int i,j; - stbi__uint16 *good; - - if (req_comp == img_n) return data; - STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - - good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); - if (good == NULL) { - STBI_FREE(data); - return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); - } - - for (j=0; j < (int) y; ++j) { - stbi__uint16 *src = data + j * x * img_n ; - stbi__uint16 *dest = good + j * x * req_comp; - - #define STBI__COMBO(a,b) ((a)*8+(b)) - #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) - // convert source image with img_n components to one with req_comp components; - // avoid switch per pixel, so use switch per scanline and massive macros - switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1,2) { dest[0]=src[0], dest[1]=0xffff; } break; - STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=0xffff; } break; - STBI__CASE(2,1) { dest[0]=src[0]; } break; - STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; - STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=0xffff; } break; - STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; - STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = 0xffff; } break; - STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; - STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = src[3]; } break; - STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; - default: STBI_ASSERT(0); - } - #undef STBI__CASE - } - - STBI_FREE(data); - return good; -} - -#ifndef STBI_NO_LINEAR -static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) -{ - int i,k,n; - float *output; - if (!data) return NULL; - output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); - if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } - // compute number of non-alpha components - if (comp & 1) n = comp; else n = comp-1; - for (i=0; i < x*y; ++i) { - for (k=0; k < n; ++k) { - output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); - } - if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; - } - STBI_FREE(data); - return output; -} -#endif - -#ifndef STBI_NO_HDR -#define stbi__float2int(x) ((int) (x)) -static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) -{ - int i,k,n; - stbi_uc *output; - if (!data) return NULL; - output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); - if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } - // compute number of non-alpha components - if (comp & 1) n = comp; else n = comp-1; - for (i=0; i < x*y; ++i) { - for (k=0; k < n; ++k) { - float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; - if (z < 0) z = 0; - if (z > 255) z = 255; - output[i*comp + k] = (stbi_uc) stbi__float2int(z); - } - if (k < comp) { - float z = data[i*comp+k] * 255 + 0.5f; - if (z < 0) z = 0; - if (z > 255) z = 255; - output[i*comp + k] = (stbi_uc) stbi__float2int(z); - } - } - STBI_FREE(data); - return output; -} -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// "baseline" JPEG/JFIF decoder -// -// simple implementation -// - doesn't support delayed output of y-dimension -// - simple interface (only one output format: 8-bit interleaved RGB) -// - doesn't try to recover corrupt jpegs -// - doesn't allow partial loading, loading multiple at once -// - still fast on x86 (copying globals into locals doesn't help x86) -// - allocates lots of intermediate memory (full size of all components) -// - non-interleaved case requires this anyway -// - allows good upsampling (see next) -// high-quality -// - upsampled channels are bilinearly interpolated, even across blocks -// - quality integer IDCT derived from IJG's 'slow' -// performance -// - fast huffman; reasonable integer IDCT -// - some SIMD kernels for common paths on targets with SSE2/NEON -// - uses a lot of intermediate memory, could cache poorly - -#ifndef STBI_NO_JPEG - -// huffman decoding acceleration -#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache - -typedef struct -{ - stbi_uc fast[1 << FAST_BITS]; - // weirdly, repacking this into AoS is a 10% speed loss, instead of a win - stbi__uint16 code[256]; - stbi_uc values[256]; - stbi_uc size[257]; - unsigned int maxcode[18]; - int delta[17]; // old 'firstsymbol' - old 'firstcode' -} stbi__huffman; - -typedef struct -{ - stbi__context *s; - stbi__huffman huff_dc[4]; - stbi__huffman huff_ac[4]; - stbi__uint16 dequant[4][64]; - stbi__int16 fast_ac[4][1 << FAST_BITS]; - -// sizes for components, interleaved MCUs - int img_h_max, img_v_max; - int img_mcu_x, img_mcu_y; - int img_mcu_w, img_mcu_h; - -// definition of jpeg image component - struct - { - int id; - int h,v; - int tq; - int hd,ha; - int dc_pred; - - int x,y,w2,h2; - stbi_uc *data; - void *raw_data, *raw_coeff; - stbi_uc *linebuf; - short *coeff; // progressive only - int coeff_w, coeff_h; // number of 8x8 coefficient blocks - } img_comp[4]; - - stbi__uint32 code_buffer; // jpeg entropy-coded buffer - int code_bits; // number of valid bits - unsigned char marker; // marker seen while filling entropy buffer - int nomore; // flag if we saw a marker so must stop - - int progressive; - int spec_start; - int spec_end; - int succ_high; - int succ_low; - int eob_run; - int jfif; - int app14_color_transform; // Adobe APP14 tag - int rgb; - - int scan_n, order[4]; - int restart_interval, todo; - -// kernels - void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); - void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); - stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); -} stbi__jpeg; - -static int stbi__build_huffman(stbi__huffman *h, int *count) -{ - int i,j,k=0; - unsigned int code; - // build size list for each symbol (from JPEG spec) - for (i=0; i < 16; ++i) - for (j=0; j < count[i]; ++j) - h->size[k++] = (stbi_uc) (i+1); - h->size[k] = 0; - - // compute actual symbols (from jpeg spec) - code = 0; - k = 0; - for(j=1; j <= 16; ++j) { - // compute delta to add to code to compute symbol id - h->delta[j] = k - code; - if (h->size[k] == j) { - while (h->size[k] == j) - h->code[k++] = (stbi__uint16) (code++); - if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); - } - // compute largest code + 1 for this size, preshifted as needed later - h->maxcode[j] = code << (16-j); - code <<= 1; - } - h->maxcode[j] = 0xffffffff; - - // build non-spec acceleration table; 255 is flag for not-accelerated - memset(h->fast, 255, 1 << FAST_BITS); - for (i=0; i < k; ++i) { - int s = h->size[i]; - if (s <= FAST_BITS) { - int c = h->code[i] << (FAST_BITS-s); - int m = 1 << (FAST_BITS-s); - for (j=0; j < m; ++j) { - h->fast[c+j] = (stbi_uc) i; - } - } - } - return 1; -} - -// build a table that decodes both magnitude and value of small ACs in -// one go. -static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) -{ - int i; - for (i=0; i < (1 << FAST_BITS); ++i) { - stbi_uc fast = h->fast[i]; - fast_ac[i] = 0; - if (fast < 255) { - int rs = h->values[fast]; - int run = (rs >> 4) & 15; - int magbits = rs & 15; - int len = h->size[fast]; - - if (magbits && len + magbits <= FAST_BITS) { - // magnitude code followed by receive_extend code - int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); - int m = 1 << (magbits - 1); - if (k < m) k += (~0U << magbits) + 1; - // if the result is small enough, we can fit it in fast_ac table - if (k >= -128 && k <= 127) - fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); - } - } - } -} - -static void stbi__grow_buffer_unsafe(stbi__jpeg *j) -{ - do { - unsigned int b = j->nomore ? 0 : stbi__get8(j->s); - if (b == 0xff) { - int c = stbi__get8(j->s); - while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes - if (c != 0) { - j->marker = (unsigned char) c; - j->nomore = 1; - return; - } - } - j->code_buffer |= b << (24 - j->code_bits); - j->code_bits += 8; - } while (j->code_bits <= 24); -} - -// (1 << n) - 1 -static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; - -// decode a jpeg huffman value from the bitstream -stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) -{ - unsigned int temp; - int c,k; - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - - // look at the top FAST_BITS and determine what symbol ID it is, - // if the code is <= FAST_BITS - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - k = h->fast[c]; - if (k < 255) { - int s = h->size[k]; - if (s > j->code_bits) - return -1; - j->code_buffer <<= s; - j->code_bits -= s; - return h->values[k]; - } - - // naive test is to shift the code_buffer down so k bits are - // valid, then test against maxcode. To speed this up, we've - // preshifted maxcode left so that it has (16-k) 0s at the - // end; in other words, regardless of the number of bits, it - // wants to be compared against something shifted to have 16; - // that way we don't need to shift inside the loop. - temp = j->code_buffer >> 16; - for (k=FAST_BITS+1 ; ; ++k) - if (temp < h->maxcode[k]) - break; - if (k == 17) { - // error! code not found - j->code_bits -= 16; - return -1; - } - - if (k > j->code_bits) - return -1; - - // convert the huffman code to the symbol id - c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; - STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); - - // convert the id to a symbol - j->code_bits -= k; - j->code_buffer <<= k; - return h->values[c]; -} - -// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); - - sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB - k = stbi_lrot(j->code_buffer, n); - STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); - j->code_buffer = k & ~stbi__bmask[n]; - k &= stbi__bmask[n]; - j->code_bits -= n; - return k + (stbi__jbias[n] & ~sgn); -} - -// get some unsigned bits -stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) -{ - unsigned int k; - if (j->code_bits < n) stbi__grow_buffer_unsafe(j); - k = stbi_lrot(j->code_buffer, n); - j->code_buffer = k & ~stbi__bmask[n]; - k &= stbi__bmask[n]; - j->code_bits -= n; - return k; -} - -stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) -{ - unsigned int k; - if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); - k = j->code_buffer; - j->code_buffer <<= 1; - --j->code_bits; - return k & 0x80000000; -} - -// given a value that's at position X in the zigzag stream, -// where does it appear in the 8x8 matrix coded as row-major? -static const stbi_uc stbi__jpeg_dezigzag[64+15] = -{ - 0, 1, 8, 16, 9, 2, 3, 10, - 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, - 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, - 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63, - // let corrupt input sample past end - 63, 63, 63, 63, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63 -}; - -// decode one 64-entry block-- -static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) -{ - int diff,dc,k; - int t; - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - t = stbi__jpeg_huff_decode(j, hdc); - if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - - // 0 all the ac values now so we can do it 32-bits at a time - memset(data,0,64*sizeof(data[0])); - - diff = t ? stbi__extend_receive(j, t) : 0; - dc = j->img_comp[b].dc_pred + diff; - j->img_comp[b].dc_pred = dc; - data[0] = (short) (dc * dequant[0]); - - // decode AC components, see JPEG spec - k = 1; - do { - unsigned int zig; - int c,r,s; - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - r = fac[c]; - if (r) { // fast-AC path - k += (r >> 4) & 15; // run - s = r & 15; // combined length - j->code_buffer <<= s; - j->code_bits -= s; - // decode into unzigzag'd location - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) ((r >> 8) * dequant[zig]); - } else { - int rs = stbi__jpeg_huff_decode(j, hac); - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (rs != 0xf0) break; // end block - k += 16; - } else { - k += r; - // decode into unzigzag'd location - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); - } - } - } while (k < 64); - return 1; -} - -static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) -{ - int diff,dc; - int t; - if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - - if (j->succ_high == 0) { - // first scan for DC coefficient, must be first - memset(data,0,64*sizeof(data[0])); // 0 all the ac values now - t = stbi__jpeg_huff_decode(j, hdc); - diff = t ? stbi__extend_receive(j, t) : 0; - - dc = j->img_comp[b].dc_pred + diff; - j->img_comp[b].dc_pred = dc; - data[0] = (short) (dc << j->succ_low); - } else { - // refinement scan for DC coefficient - if (stbi__jpeg_get_bit(j)) - data[0] += (short) (1 << j->succ_low); - } - return 1; -} - -// @OPTIMIZE: store non-zigzagged during the decode passes, -// and only de-zigzag when dequantizing -static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) -{ - int k; - if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); - - if (j->succ_high == 0) { - int shift = j->succ_low; - - if (j->eob_run) { - --j->eob_run; - return 1; - } - - k = j->spec_start; - do { - unsigned int zig; - int c,r,s; - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - r = fac[c]; - if (r) { // fast-AC path - k += (r >> 4) & 15; // run - s = r & 15; // combined length - j->code_buffer <<= s; - j->code_bits -= s; - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) ((r >> 8) << shift); - } else { - int rs = stbi__jpeg_huff_decode(j, hac); - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (r < 15) { - j->eob_run = (1 << r); - if (r) - j->eob_run += stbi__jpeg_get_bits(j, r); - --j->eob_run; - break; - } - k += 16; - } else { - k += r; - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) (stbi__extend_receive(j,s) << shift); - } - } - } while (k <= j->spec_end); - } else { - // refinement scan for these AC coefficients - - short bit = (short) (1 << j->succ_low); - - if (j->eob_run) { - --j->eob_run; - for (k = j->spec_start; k <= j->spec_end; ++k) { - short *p = &data[stbi__jpeg_dezigzag[k]]; - if (*p != 0) - if (stbi__jpeg_get_bit(j)) - if ((*p & bit)==0) { - if (*p > 0) - *p += bit; - else - *p -= bit; - } - } - } else { - k = j->spec_start; - do { - int r,s; - int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (r < 15) { - j->eob_run = (1 << r) - 1; - if (r) - j->eob_run += stbi__jpeg_get_bits(j, r); - r = 64; // force end of block - } else { - // r=15 s=0 should write 16 0s, so we just do - // a run of 15 0s and then write s (which is 0), - // so we don't have to do anything special here - } - } else { - if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); - // sign bit - if (stbi__jpeg_get_bit(j)) - s = bit; - else - s = -bit; - } - - // advance by r - while (k <= j->spec_end) { - short *p = &data[stbi__jpeg_dezigzag[k++]]; - if (*p != 0) { - if (stbi__jpeg_get_bit(j)) - if ((*p & bit)==0) { - if (*p > 0) - *p += bit; - else - *p -= bit; - } - } else { - if (r == 0) { - *p = (short) s; - break; - } - --r; - } - } - } while (k <= j->spec_end); - } - } - return 1; -} - -// take a -128..127 value and stbi__clamp it and convert to 0..255 -stbi_inline static stbi_uc stbi__clamp(int x) -{ - // trick to use a single test to catch both cases - if ((unsigned int) x > 255) { - if (x < 0) return 0; - if (x > 255) return 255; - } - return (stbi_uc) x; -} - -#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) -#define stbi__fsh(x) ((x) * 4096) - -// derived from jidctint -- DCT_ISLOW -#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ - int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ - p2 = s2; \ - p3 = s6; \ - p1 = (p2+p3) * stbi__f2f(0.5411961f); \ - t2 = p1 + p3*stbi__f2f(-1.847759065f); \ - t3 = p1 + p2*stbi__f2f( 0.765366865f); \ - p2 = s0; \ - p3 = s4; \ - t0 = stbi__fsh(p2+p3); \ - t1 = stbi__fsh(p2-p3); \ - x0 = t0+t3; \ - x3 = t0-t3; \ - x1 = t1+t2; \ - x2 = t1-t2; \ - t0 = s7; \ - t1 = s5; \ - t2 = s3; \ - t3 = s1; \ - p3 = t0+t2; \ - p4 = t1+t3; \ - p1 = t0+t3; \ - p2 = t1+t2; \ - p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ - t0 = t0*stbi__f2f( 0.298631336f); \ - t1 = t1*stbi__f2f( 2.053119869f); \ - t2 = t2*stbi__f2f( 3.072711026f); \ - t3 = t3*stbi__f2f( 1.501321110f); \ - p1 = p5 + p1*stbi__f2f(-0.899976223f); \ - p2 = p5 + p2*stbi__f2f(-2.562915447f); \ - p3 = p3*stbi__f2f(-1.961570560f); \ - p4 = p4*stbi__f2f(-0.390180644f); \ - t3 += p1+p4; \ - t2 += p2+p3; \ - t1 += p2+p4; \ - t0 += p1+p3; - -static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) -{ - int i,val[64],*v=val; - stbi_uc *o; - short *d = data; - - // columns - for (i=0; i < 8; ++i,++d, ++v) { - // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing - if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 - && d[40]==0 && d[48]==0 && d[56]==0) { - // no shortcut 0 seconds - // (1|2|3|4|5|6|7)==0 0 seconds - // all separate -0.047 seconds - // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds - int dcterm = d[0]*4; - v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; - } else { - STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) - // constants scaled things up by 1<<12; let's bring them back - // down, but keep 2 extra bits of precision - x0 += 512; x1 += 512; x2 += 512; x3 += 512; - v[ 0] = (x0+t3) >> 10; - v[56] = (x0-t3) >> 10; - v[ 8] = (x1+t2) >> 10; - v[48] = (x1-t2) >> 10; - v[16] = (x2+t1) >> 10; - v[40] = (x2-t1) >> 10; - v[24] = (x3+t0) >> 10; - v[32] = (x3-t0) >> 10; - } - } - - for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { - // no fast case since the first 1D IDCT spread components out - STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) - // constants scaled things up by 1<<12, plus we had 1<<2 from first - // loop, plus horizontal and vertical each scale by sqrt(8) so together - // we've got an extra 1<<3, so 1<<17 total we need to remove. - // so we want to round that, which means adding 0.5 * 1<<17, - // aka 65536. Also, we'll end up with -128 to 127 that we want - // to encode as 0..255 by adding 128, so we'll add that before the shift - x0 += 65536 + (128<<17); - x1 += 65536 + (128<<17); - x2 += 65536 + (128<<17); - x3 += 65536 + (128<<17); - // tried computing the shifts into temps, or'ing the temps to see - // if any were out of range, but that was slower - o[0] = stbi__clamp((x0+t3) >> 17); - o[7] = stbi__clamp((x0-t3) >> 17); - o[1] = stbi__clamp((x1+t2) >> 17); - o[6] = stbi__clamp((x1-t2) >> 17); - o[2] = stbi__clamp((x2+t1) >> 17); - o[5] = stbi__clamp((x2-t1) >> 17); - o[3] = stbi__clamp((x3+t0) >> 17); - o[4] = stbi__clamp((x3-t0) >> 17); - } -} - -#ifdef STBI_SSE2 -// sse2 integer IDCT. not the fastest possible implementation but it -// produces bit-identical results to the generic C version so it's -// fully "transparent". -static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) -{ - // This is constructed to match our regular (generic) integer IDCT exactly. - __m128i row0, row1, row2, row3, row4, row5, row6, row7; - __m128i tmp; - - // dot product constant: even elems=x, odd elems=y - #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) - - // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) - // out(1) = c1[even]*x + c1[odd]*y - #define dct_rot(out0,out1, x,y,c0,c1) \ - __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ - __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ - __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ - __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ - __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ - __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) - - // out = in << 12 (in 16-bit, out 32-bit) - #define dct_widen(out, in) \ - __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ - __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) - - // wide add - #define dct_wadd(out, a, b) \ - __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ - __m128i out##_h = _mm_add_epi32(a##_h, b##_h) - - // wide sub - #define dct_wsub(out, a, b) \ - __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ - __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) - - // butterfly a/b, add bias, then shift by "s" and pack - #define dct_bfly32o(out0, out1, a,b,bias,s) \ - { \ - __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ - __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ - dct_wadd(sum, abiased, b); \ - dct_wsub(dif, abiased, b); \ - out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ - out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ - } - - // 8-bit interleave step (for transposes) - #define dct_interleave8(a, b) \ - tmp = a; \ - a = _mm_unpacklo_epi8(a, b); \ - b = _mm_unpackhi_epi8(tmp, b) - - // 16-bit interleave step (for transposes) - #define dct_interleave16(a, b) \ - tmp = a; \ - a = _mm_unpacklo_epi16(a, b); \ - b = _mm_unpackhi_epi16(tmp, b) - - #define dct_pass(bias,shift) \ - { \ - /* even part */ \ - dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ - __m128i sum04 = _mm_add_epi16(row0, row4); \ - __m128i dif04 = _mm_sub_epi16(row0, row4); \ - dct_widen(t0e, sum04); \ - dct_widen(t1e, dif04); \ - dct_wadd(x0, t0e, t3e); \ - dct_wsub(x3, t0e, t3e); \ - dct_wadd(x1, t1e, t2e); \ - dct_wsub(x2, t1e, t2e); \ - /* odd part */ \ - dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ - dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ - __m128i sum17 = _mm_add_epi16(row1, row7); \ - __m128i sum35 = _mm_add_epi16(row3, row5); \ - dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ - dct_wadd(x4, y0o, y4o); \ - dct_wadd(x5, y1o, y5o); \ - dct_wadd(x6, y2o, y5o); \ - dct_wadd(x7, y3o, y4o); \ - dct_bfly32o(row0,row7, x0,x7,bias,shift); \ - dct_bfly32o(row1,row6, x1,x6,bias,shift); \ - dct_bfly32o(row2,row5, x2,x5,bias,shift); \ - dct_bfly32o(row3,row4, x3,x4,bias,shift); \ - } - - __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); - __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); - __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); - __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); - __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); - __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); - __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); - __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); - - // rounding biases in column/row passes, see stbi__idct_block for explanation. - __m128i bias_0 = _mm_set1_epi32(512); - __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); - - // load - row0 = _mm_load_si128((const __m128i *) (data + 0*8)); - row1 = _mm_load_si128((const __m128i *) (data + 1*8)); - row2 = _mm_load_si128((const __m128i *) (data + 2*8)); - row3 = _mm_load_si128((const __m128i *) (data + 3*8)); - row4 = _mm_load_si128((const __m128i *) (data + 4*8)); - row5 = _mm_load_si128((const __m128i *) (data + 5*8)); - row6 = _mm_load_si128((const __m128i *) (data + 6*8)); - row7 = _mm_load_si128((const __m128i *) (data + 7*8)); - - // column pass - dct_pass(bias_0, 10); - - { - // 16bit 8x8 transpose pass 1 - dct_interleave16(row0, row4); - dct_interleave16(row1, row5); - dct_interleave16(row2, row6); - dct_interleave16(row3, row7); - - // transpose pass 2 - dct_interleave16(row0, row2); - dct_interleave16(row1, row3); - dct_interleave16(row4, row6); - dct_interleave16(row5, row7); - - // transpose pass 3 - dct_interleave16(row0, row1); - dct_interleave16(row2, row3); - dct_interleave16(row4, row5); - dct_interleave16(row6, row7); - } - - // row pass - dct_pass(bias_1, 17); - - { - // pack - __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 - __m128i p1 = _mm_packus_epi16(row2, row3); - __m128i p2 = _mm_packus_epi16(row4, row5); - __m128i p3 = _mm_packus_epi16(row6, row7); - - // 8bit 8x8 transpose pass 1 - dct_interleave8(p0, p2); // a0e0a1e1... - dct_interleave8(p1, p3); // c0g0c1g1... - - // transpose pass 2 - dct_interleave8(p0, p1); // a0c0e0g0... - dct_interleave8(p2, p3); // b0d0f0h0... - - // transpose pass 3 - dct_interleave8(p0, p2); // a0b0c0d0... - dct_interleave8(p1, p3); // a4b4c4d4... - - // store - _mm_storel_epi64((__m128i *) out, p0); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p2); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p1); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p3); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); - } - -#undef dct_const -#undef dct_rot -#undef dct_widen -#undef dct_wadd -#undef dct_wsub -#undef dct_bfly32o -#undef dct_interleave8 -#undef dct_interleave16 -#undef dct_pass -} - -#endif // STBI_SSE2 - -#ifdef STBI_NEON - -// NEON integer IDCT. should produce bit-identical -// results to the generic C version. -static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) -{ - int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; - - int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); - int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); - int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); - int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); - int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); - int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); - int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); - int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); - int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); - int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); - int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); - int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); - -#define dct_long_mul(out, inq, coeff) \ - int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ - int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) - -#define dct_long_mac(out, acc, inq, coeff) \ - int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ - int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) - -#define dct_widen(out, inq) \ - int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ - int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) - -// wide add -#define dct_wadd(out, a, b) \ - int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ - int32x4_t out##_h = vaddq_s32(a##_h, b##_h) - -// wide sub -#define dct_wsub(out, a, b) \ - int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ - int32x4_t out##_h = vsubq_s32(a##_h, b##_h) - -// butterfly a/b, then shift using "shiftop" by "s" and pack -#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ - { \ - dct_wadd(sum, a, b); \ - dct_wsub(dif, a, b); \ - out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ - out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ - } - -#define dct_pass(shiftop, shift) \ - { \ - /* even part */ \ - int16x8_t sum26 = vaddq_s16(row2, row6); \ - dct_long_mul(p1e, sum26, rot0_0); \ - dct_long_mac(t2e, p1e, row6, rot0_1); \ - dct_long_mac(t3e, p1e, row2, rot0_2); \ - int16x8_t sum04 = vaddq_s16(row0, row4); \ - int16x8_t dif04 = vsubq_s16(row0, row4); \ - dct_widen(t0e, sum04); \ - dct_widen(t1e, dif04); \ - dct_wadd(x0, t0e, t3e); \ - dct_wsub(x3, t0e, t3e); \ - dct_wadd(x1, t1e, t2e); \ - dct_wsub(x2, t1e, t2e); \ - /* odd part */ \ - int16x8_t sum15 = vaddq_s16(row1, row5); \ - int16x8_t sum17 = vaddq_s16(row1, row7); \ - int16x8_t sum35 = vaddq_s16(row3, row5); \ - int16x8_t sum37 = vaddq_s16(row3, row7); \ - int16x8_t sumodd = vaddq_s16(sum17, sum35); \ - dct_long_mul(p5o, sumodd, rot1_0); \ - dct_long_mac(p1o, p5o, sum17, rot1_1); \ - dct_long_mac(p2o, p5o, sum35, rot1_2); \ - dct_long_mul(p3o, sum37, rot2_0); \ - dct_long_mul(p4o, sum15, rot2_1); \ - dct_wadd(sump13o, p1o, p3o); \ - dct_wadd(sump24o, p2o, p4o); \ - dct_wadd(sump23o, p2o, p3o); \ - dct_wadd(sump14o, p1o, p4o); \ - dct_long_mac(x4, sump13o, row7, rot3_0); \ - dct_long_mac(x5, sump24o, row5, rot3_1); \ - dct_long_mac(x6, sump23o, row3, rot3_2); \ - dct_long_mac(x7, sump14o, row1, rot3_3); \ - dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ - dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ - dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ - dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ - } - - // load - row0 = vld1q_s16(data + 0*8); - row1 = vld1q_s16(data + 1*8); - row2 = vld1q_s16(data + 2*8); - row3 = vld1q_s16(data + 3*8); - row4 = vld1q_s16(data + 4*8); - row5 = vld1q_s16(data + 5*8); - row6 = vld1q_s16(data + 6*8); - row7 = vld1q_s16(data + 7*8); - - // add DC bias - row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); - - // column pass - dct_pass(vrshrn_n_s32, 10); - - // 16bit 8x8 transpose - { -// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. -// whether compilers actually get this is another story, sadly. -#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } -#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } -#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } - - // pass 1 - dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 - dct_trn16(row2, row3); - dct_trn16(row4, row5); - dct_trn16(row6, row7); - - // pass 2 - dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 - dct_trn32(row1, row3); - dct_trn32(row4, row6); - dct_trn32(row5, row7); - - // pass 3 - dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 - dct_trn64(row1, row5); - dct_trn64(row2, row6); - dct_trn64(row3, row7); - -#undef dct_trn16 -#undef dct_trn32 -#undef dct_trn64 - } - - // row pass - // vrshrn_n_s32 only supports shifts up to 16, we need - // 17. so do a non-rounding shift of 16 first then follow - // up with a rounding shift by 1. - dct_pass(vshrn_n_s32, 16); - - { - // pack and round - uint8x8_t p0 = vqrshrun_n_s16(row0, 1); - uint8x8_t p1 = vqrshrun_n_s16(row1, 1); - uint8x8_t p2 = vqrshrun_n_s16(row2, 1); - uint8x8_t p3 = vqrshrun_n_s16(row3, 1); - uint8x8_t p4 = vqrshrun_n_s16(row4, 1); - uint8x8_t p5 = vqrshrun_n_s16(row5, 1); - uint8x8_t p6 = vqrshrun_n_s16(row6, 1); - uint8x8_t p7 = vqrshrun_n_s16(row7, 1); - - // again, these can translate into one instruction, but often don't. -#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } -#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } -#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } - - // sadly can't use interleaved stores here since we only write - // 8 bytes to each scan line! - - // 8x8 8-bit transpose pass 1 - dct_trn8_8(p0, p1); - dct_trn8_8(p2, p3); - dct_trn8_8(p4, p5); - dct_trn8_8(p6, p7); - - // pass 2 - dct_trn8_16(p0, p2); - dct_trn8_16(p1, p3); - dct_trn8_16(p4, p6); - dct_trn8_16(p5, p7); - - // pass 3 - dct_trn8_32(p0, p4); - dct_trn8_32(p1, p5); - dct_trn8_32(p2, p6); - dct_trn8_32(p3, p7); - - // store - vst1_u8(out, p0); out += out_stride; - vst1_u8(out, p1); out += out_stride; - vst1_u8(out, p2); out += out_stride; - vst1_u8(out, p3); out += out_stride; - vst1_u8(out, p4); out += out_stride; - vst1_u8(out, p5); out += out_stride; - vst1_u8(out, p6); out += out_stride; - vst1_u8(out, p7); - -#undef dct_trn8_8 -#undef dct_trn8_16 -#undef dct_trn8_32 - } - -#undef dct_long_mul -#undef dct_long_mac -#undef dct_widen -#undef dct_wadd -#undef dct_wsub -#undef dct_bfly32o -#undef dct_pass -} - -#endif // STBI_NEON - -#define STBI__MARKER_none 0xff -// if there's a pending marker from the entropy stream, return that -// otherwise, fetch from the stream and get a marker. if there's no -// marker, return 0xff, which is never a valid marker value -static stbi_uc stbi__get_marker(stbi__jpeg *j) -{ - stbi_uc x; - if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } - x = stbi__get8(j->s); - if (x != 0xff) return STBI__MARKER_none; - while (x == 0xff) - x = stbi__get8(j->s); // consume repeated 0xff fill bytes - return x; -} - -// in each scan, we'll have scan_n components, and the order -// of the components is specified by order[] -#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) - -// after a restart interval, stbi__jpeg_reset the entropy decoder and -// the dc prediction -static void stbi__jpeg_reset(stbi__jpeg *j) -{ - j->code_bits = 0; - j->code_buffer = 0; - j->nomore = 0; - j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; - j->marker = STBI__MARKER_none; - j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; - j->eob_run = 0; - // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, - // since we don't even allow 1<<30 pixels -} - -static int stbi__parse_entropy_coded_data(stbi__jpeg *z) -{ - stbi__jpeg_reset(z); - if (!z->progressive) { - if (z->scan_n == 1) { - int i,j; - STBI_SIMD_ALIGN(short, data[64]); - int n = z->order[0]; - // non-interleaved data, we just need to process one block at a time, - // in trivial scanline order - // number of blocks to do just depends on how many actual "pixels" this - // component has, independent of interleaved MCU blocking and such - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); - // every data block is an MCU, so countdown the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - // if it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } else { // interleaved - int i,j,k,x,y; - STBI_SIMD_ALIGN(short, data[64]); - for (j=0; j < z->img_mcu_y; ++j) { - for (i=0; i < z->img_mcu_x; ++i) { - // scan an interleaved mcu... process scan_n components in order - for (k=0; k < z->scan_n; ++k) { - int n = z->order[k]; - // scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (y=0; y < z->img_comp[n].v; ++y) { - for (x=0; x < z->img_comp[n].h; ++x) { - int x2 = (i*z->img_comp[n].h + x)*8; - int y2 = (j*z->img_comp[n].v + y)*8; - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); - } - } - } - // after all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } - } else { - if (z->scan_n == 1) { - int i,j; - int n = z->order[0]; - // non-interleaved data, we just need to process one block at a time, - // in trivial scanline order - // number of blocks to do just depends on how many actual "pixels" this - // component has, independent of interleaved MCU blocking and such - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); - if (z->spec_start == 0) { - if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) - return 0; - } else { - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) - return 0; - } - // every data block is an MCU, so countdown the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } else { // interleaved - int i,j,k,x,y; - for (j=0; j < z->img_mcu_y; ++j) { - for (i=0; i < z->img_mcu_x; ++i) { - // scan an interleaved mcu... process scan_n components in order - for (k=0; k < z->scan_n; ++k) { - int n = z->order[k]; - // scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (y=0; y < z->img_comp[n].v; ++y) { - for (x=0; x < z->img_comp[n].h; ++x) { - int x2 = (i*z->img_comp[n].h + x); - int y2 = (j*z->img_comp[n].v + y); - short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); - if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) - return 0; - } - } - } - // after all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } - } -} - -static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) -{ - int i; - for (i=0; i < 64; ++i) - data[i] *= dequant[i]; -} - -static void stbi__jpeg_finish(stbi__jpeg *z) -{ - if (z->progressive) { - // dequantize and idct the data - int i,j,n; - for (n=0; n < z->s->img_n; ++n) { - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); - stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); - } - } - } - } -} - -static int stbi__process_marker(stbi__jpeg *z, int m) -{ - int L; - switch (m) { - case STBI__MARKER_none: // no marker found - return stbi__err("expected marker","Corrupt JPEG"); - - case 0xDD: // DRI - specify restart interval - if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); - z->restart_interval = stbi__get16be(z->s); - return 1; - - case 0xDB: // DQT - define quantization table - L = stbi__get16be(z->s)-2; - while (L > 0) { - int q = stbi__get8(z->s); - int p = q >> 4, sixteen = (p != 0); - int t = q & 15,i; - if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); - if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); - - for (i=0; i < 64; ++i) - z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); - L -= (sixteen ? 129 : 65); - } - return L==0; - - case 0xC4: // DHT - define huffman table - L = stbi__get16be(z->s)-2; - while (L > 0) { - stbi_uc *v; - int sizes[16],i,n=0; - int q = stbi__get8(z->s); - int tc = q >> 4; - int th = q & 15; - if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); - for (i=0; i < 16; ++i) { - sizes[i] = stbi__get8(z->s); - n += sizes[i]; - } - L -= 17; - if (tc == 0) { - if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; - v = z->huff_dc[th].values; - } else { - if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; - v = z->huff_ac[th].values; - } - for (i=0; i < n; ++i) - v[i] = stbi__get8(z->s); - if (tc != 0) - stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); - L -= n; - } - return L==0; - } - - // check for comment block or APP blocks - if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { - L = stbi__get16be(z->s); - if (L < 2) { - if (m == 0xFE) - return stbi__err("bad COM len","Corrupt JPEG"); - else - return stbi__err("bad APP len","Corrupt JPEG"); - } - L -= 2; - - if (m == 0xE0 && L >= 5) { // JFIF APP0 segment - static const unsigned char tag[5] = {'J','F','I','F','\0'}; - int ok = 1; - int i; - for (i=0; i < 5; ++i) - if (stbi__get8(z->s) != tag[i]) - ok = 0; - L -= 5; - if (ok) - z->jfif = 1; - } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment - static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; - int ok = 1; - int i; - for (i=0; i < 6; ++i) - if (stbi__get8(z->s) != tag[i]) - ok = 0; - L -= 6; - if (ok) { - stbi__get8(z->s); // version - stbi__get16be(z->s); // flags0 - stbi__get16be(z->s); // flags1 - z->app14_color_transform = stbi__get8(z->s); // color transform - L -= 6; - } - } - - stbi__skip(z->s, L); - return 1; - } - - return stbi__err("unknown marker","Corrupt JPEG"); -} - -// after we see SOS -static int stbi__process_scan_header(stbi__jpeg *z) -{ - int i; - int Ls = stbi__get16be(z->s); - z->scan_n = stbi__get8(z->s); - if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); - if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); - for (i=0; i < z->scan_n; ++i) { - int id = stbi__get8(z->s), which; - int q = stbi__get8(z->s); - for (which = 0; which < z->s->img_n; ++which) - if (z->img_comp[which].id == id) - break; - if (which == z->s->img_n) return 0; // no match - z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); - z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); - z->order[i] = which; - } - - { - int aa; - z->spec_start = stbi__get8(z->s); - z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 - aa = stbi__get8(z->s); - z->succ_high = (aa >> 4); - z->succ_low = (aa & 15); - if (z->progressive) { - if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) - return stbi__err("bad SOS", "Corrupt JPEG"); - } else { - if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); - if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); - z->spec_end = 63; - } - } - - return 1; -} - -static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) -{ - int i; - for (i=0; i < ncomp; ++i) { - if (z->img_comp[i].raw_data) { - STBI_FREE(z->img_comp[i].raw_data); - z->img_comp[i].raw_data = NULL; - z->img_comp[i].data = NULL; - } - if (z->img_comp[i].raw_coeff) { - STBI_FREE(z->img_comp[i].raw_coeff); - z->img_comp[i].raw_coeff = 0; - z->img_comp[i].coeff = 0; - } - if (z->img_comp[i].linebuf) { - STBI_FREE(z->img_comp[i].linebuf); - z->img_comp[i].linebuf = NULL; - } - } - return why; -} - -static int stbi__process_frame_header(stbi__jpeg *z, int scan) -{ - stbi__context *s = z->s; - int Lf,p,i,q, h_max=1,v_max=1,c; - Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG - p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline - s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG - s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires - c = stbi__get8(s); - if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); - s->img_n = c; - for (i=0; i < c; ++i) { - z->img_comp[i].data = NULL; - z->img_comp[i].linebuf = NULL; - } - - if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); - - z->rgb = 0; - for (i=0; i < s->img_n; ++i) { - static const unsigned char rgb[3] = { 'R', 'G', 'B' }; - z->img_comp[i].id = stbi__get8(s); - if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) - ++z->rgb; - q = stbi__get8(s); - z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); - z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); - z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); - } - - if (scan != STBI__SCAN_load) return 1; - - if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); - - for (i=0; i < s->img_n; ++i) { - if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; - if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; - } - - // compute interleaved mcu info - z->img_h_max = h_max; - z->img_v_max = v_max; - z->img_mcu_w = h_max * 8; - z->img_mcu_h = v_max * 8; - // these sizes can't be more than 17 bits - z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; - z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; - - for (i=0; i < s->img_n; ++i) { - // number of effective pixels (e.g. for non-interleaved MCU) - z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; - z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; - // to simplify generation, we'll allocate enough memory to decode - // the bogus oversized data from using interleaved MCUs and their - // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't - // discard the extra data until colorspace conversion - // - // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) - // so these muls can't overflow with 32-bit ints (which we require) - z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; - z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; - z->img_comp[i].coeff = 0; - z->img_comp[i].raw_coeff = 0; - z->img_comp[i].linebuf = NULL; - z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); - if (z->img_comp[i].raw_data == NULL) - return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); - // align blocks for idct using mmx/sse - z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); - if (z->progressive) { - // w2, h2 are multiples of 8 (see above) - z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; - z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; - z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); - if (z->img_comp[i].raw_coeff == NULL) - return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); - z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); - } - } - - return 1; -} - -// use comparisons since in some cases we handle more than one case (e.g. SOF) -#define stbi__DNL(x) ((x) == 0xdc) -#define stbi__SOI(x) ((x) == 0xd8) -#define stbi__EOI(x) ((x) == 0xd9) -#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) -#define stbi__SOS(x) ((x) == 0xda) - -#define stbi__SOF_progressive(x) ((x) == 0xc2) - -static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) -{ - int m; - z->jfif = 0; - z->app14_color_transform = -1; // valid values are 0,1,2 - z->marker = STBI__MARKER_none; // initialize cached marker to empty - m = stbi__get_marker(z); - if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); - if (scan == STBI__SCAN_type) return 1; - m = stbi__get_marker(z); - while (!stbi__SOF(m)) { - if (!stbi__process_marker(z,m)) return 0; - m = stbi__get_marker(z); - while (m == STBI__MARKER_none) { - // some files have extra padding after their blocks, so ok, we'll scan - if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); - m = stbi__get_marker(z); - } - } - z->progressive = stbi__SOF_progressive(m); - if (!stbi__process_frame_header(z, scan)) return 0; - return 1; -} - -// decode image to YCbCr format -static int stbi__decode_jpeg_image(stbi__jpeg *j) -{ - int m; - for (m = 0; m < 4; m++) { - j->img_comp[m].raw_data = NULL; - j->img_comp[m].raw_coeff = NULL; - } - j->restart_interval = 0; - if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; - m = stbi__get_marker(j); - while (!stbi__EOI(m)) { - if (stbi__SOS(m)) { - if (!stbi__process_scan_header(j)) return 0; - if (!stbi__parse_entropy_coded_data(j)) return 0; - if (j->marker == STBI__MARKER_none ) { - // handle 0s at the end of image data from IP Kamera 9060 - while (!stbi__at_eof(j->s)) { - int x = stbi__get8(j->s); - if (x == 255) { - j->marker = stbi__get8(j->s); - break; - } - } - // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 - } - } else if (stbi__DNL(m)) { - int Ld = stbi__get16be(j->s); - stbi__uint32 NL = stbi__get16be(j->s); - if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); - if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); - } else { - if (!stbi__process_marker(j, m)) return 0; - } - m = stbi__get_marker(j); - } - if (j->progressive) - stbi__jpeg_finish(j); - return 1; -} - -// static jfif-centered resampling (across block boundaries) - -typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, - int w, int hs); - -#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) - -static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - STBI_NOTUSED(out); - STBI_NOTUSED(in_far); - STBI_NOTUSED(w); - STBI_NOTUSED(hs); - return in_near; -} - -static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate two samples vertically for every one in input - int i; - STBI_NOTUSED(hs); - for (i=0; i < w; ++i) - out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); - return out; -} - -static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate two samples horizontally for every one in input - int i; - stbi_uc *input = in_near; - - if (w == 1) { - // if only one sample, can't do any interpolation - out[0] = out[1] = input[0]; - return out; - } - - out[0] = input[0]; - out[1] = stbi__div4(input[0]*3 + input[1] + 2); - for (i=1; i < w-1; ++i) { - int n = 3*input[i]+2; - out[i*2+0] = stbi__div4(n+input[i-1]); - out[i*2+1] = stbi__div4(n+input[i+1]); - } - out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); - out[i*2+1] = input[w-1]; - - STBI_NOTUSED(in_far); - STBI_NOTUSED(hs); - - return out; -} - -#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) - -static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate 2x2 samples for every one in input - int i,t0,t1; - if (w == 1) { - out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); - return out; - } - - t1 = 3*in_near[0] + in_far[0]; - out[0] = stbi__div4(t1+2); - for (i=1; i < w; ++i) { - t0 = t1; - t1 = 3*in_near[i]+in_far[i]; - out[i*2-1] = stbi__div16(3*t0 + t1 + 8); - out[i*2 ] = stbi__div16(3*t1 + t0 + 8); - } - out[w*2-1] = stbi__div4(t1+2); - - STBI_NOTUSED(hs); - - return out; -} - -#if defined(STBI_SSE2) || defined(STBI_NEON) -static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate 2x2 samples for every one in input - int i=0,t0,t1; - - if (w == 1) { - out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); - return out; - } - - t1 = 3*in_near[0] + in_far[0]; - // process groups of 8 pixels for as long as we can. - // note we can't handle the last pixel in a row in this loop - // because we need to handle the filter boundary conditions. - for (; i < ((w-1) & ~7); i += 8) { -#if defined(STBI_SSE2) - // load and perform the vertical filtering pass - // this uses 3*x + y = 4*x + (y - x) - __m128i zero = _mm_setzero_si128(); - __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); - __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); - __m128i farw = _mm_unpacklo_epi8(farb, zero); - __m128i nearw = _mm_unpacklo_epi8(nearb, zero); - __m128i diff = _mm_sub_epi16(farw, nearw); - __m128i nears = _mm_slli_epi16(nearw, 2); - __m128i curr = _mm_add_epi16(nears, diff); // current row - - // horizontal filter works the same based on shifted vers of current - // row. "prev" is current row shifted right by 1 pixel; we need to - // insert the previous pixel value (from t1). - // "next" is current row shifted left by 1 pixel, with first pixel - // of next block of 8 pixels added in. - __m128i prv0 = _mm_slli_si128(curr, 2); - __m128i nxt0 = _mm_srli_si128(curr, 2); - __m128i prev = _mm_insert_epi16(prv0, t1, 0); - __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); - - // horizontal filter, polyphase implementation since it's convenient: - // even pixels = 3*cur + prev = cur*4 + (prev - cur) - // odd pixels = 3*cur + next = cur*4 + (next - cur) - // note the shared term. - __m128i bias = _mm_set1_epi16(8); - __m128i curs = _mm_slli_epi16(curr, 2); - __m128i prvd = _mm_sub_epi16(prev, curr); - __m128i nxtd = _mm_sub_epi16(next, curr); - __m128i curb = _mm_add_epi16(curs, bias); - __m128i even = _mm_add_epi16(prvd, curb); - __m128i odd = _mm_add_epi16(nxtd, curb); - - // interleave even and odd pixels, then undo scaling. - __m128i int0 = _mm_unpacklo_epi16(even, odd); - __m128i int1 = _mm_unpackhi_epi16(even, odd); - __m128i de0 = _mm_srli_epi16(int0, 4); - __m128i de1 = _mm_srli_epi16(int1, 4); - - // pack and write output - __m128i outv = _mm_packus_epi16(de0, de1); - _mm_storeu_si128((__m128i *) (out + i*2), outv); -#elif defined(STBI_NEON) - // load and perform the vertical filtering pass - // this uses 3*x + y = 4*x + (y - x) - uint8x8_t farb = vld1_u8(in_far + i); - uint8x8_t nearb = vld1_u8(in_near + i); - int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); - int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); - int16x8_t curr = vaddq_s16(nears, diff); // current row - - // horizontal filter works the same based on shifted vers of current - // row. "prev" is current row shifted right by 1 pixel; we need to - // insert the previous pixel value (from t1). - // "next" is current row shifted left by 1 pixel, with first pixel - // of next block of 8 pixels added in. - int16x8_t prv0 = vextq_s16(curr, curr, 7); - int16x8_t nxt0 = vextq_s16(curr, curr, 1); - int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); - int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); - - // horizontal filter, polyphase implementation since it's convenient: - // even pixels = 3*cur + prev = cur*4 + (prev - cur) - // odd pixels = 3*cur + next = cur*4 + (next - cur) - // note the shared term. - int16x8_t curs = vshlq_n_s16(curr, 2); - int16x8_t prvd = vsubq_s16(prev, curr); - int16x8_t nxtd = vsubq_s16(next, curr); - int16x8_t even = vaddq_s16(curs, prvd); - int16x8_t odd = vaddq_s16(curs, nxtd); - - // undo scaling and round, then store with even/odd phases interleaved - uint8x8x2_t o; - o.val[0] = vqrshrun_n_s16(even, 4); - o.val[1] = vqrshrun_n_s16(odd, 4); - vst2_u8(out + i*2, o); -#endif - - // "previous" value for next iter - t1 = 3*in_near[i+7] + in_far[i+7]; - } - - t0 = t1; - t1 = 3*in_near[i] + in_far[i]; - out[i*2] = stbi__div16(3*t1 + t0 + 8); - - for (++i; i < w; ++i) { - t0 = t1; - t1 = 3*in_near[i]+in_far[i]; - out[i*2-1] = stbi__div16(3*t0 + t1 + 8); - out[i*2 ] = stbi__div16(3*t1 + t0 + 8); - } - out[w*2-1] = stbi__div4(t1+2); - - STBI_NOTUSED(hs); - - return out; -} -#endif - -static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // resample with nearest-neighbor - int i,j; - STBI_NOTUSED(in_far); - for (i=0; i < w; ++i) - for (j=0; j < hs; ++j) - out[i*hs+j] = in_near[i]; - return out; -} - -// this is a reduced-precision calculation of YCbCr-to-RGB introduced -// to make sure the code produces the same results in both SIMD and scalar -#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) -static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) -{ - int i; - for (i=0; i < count; ++i) { - int y_fixed = (y[i] << 20) + (1<<19); // rounding - int r,g,b; - int cr = pcr[i] - 128; - int cb = pcb[i] - 128; - r = y_fixed + cr* stbi__float2fixed(1.40200f); - g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); - b = y_fixed + cb* stbi__float2fixed(1.77200f); - r >>= 20; - g >>= 20; - b >>= 20; - if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } - if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } - if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } - out[0] = (stbi_uc)r; - out[1] = (stbi_uc)g; - out[2] = (stbi_uc)b; - out[3] = 255; - out += step; - } -} - -#if defined(STBI_SSE2) || defined(STBI_NEON) -static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) -{ - int i = 0; - -#ifdef STBI_SSE2 - // step == 3 is pretty ugly on the final interleave, and i'm not convinced - // it's useful in practice (you wouldn't use it for textures, for example). - // so just accelerate step == 4 case. - if (step == 4) { - // this is a fairly straightforward implementation and not super-optimized. - __m128i signflip = _mm_set1_epi8(-0x80); - __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); - __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); - __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); - __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); - __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); - __m128i xw = _mm_set1_epi16(255); // alpha channel - - for (; i+7 < count; i += 8) { - // load - __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); - __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); - __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); - __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 - __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 - - // unpack to short (and left-shift cr, cb by 8) - __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); - __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); - __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); - - // color transform - __m128i yws = _mm_srli_epi16(yw, 4); - __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); - __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); - __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); - __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); - __m128i rws = _mm_add_epi16(cr0, yws); - __m128i gwt = _mm_add_epi16(cb0, yws); - __m128i bws = _mm_add_epi16(yws, cb1); - __m128i gws = _mm_add_epi16(gwt, cr1); - - // descale - __m128i rw = _mm_srai_epi16(rws, 4); - __m128i bw = _mm_srai_epi16(bws, 4); - __m128i gw = _mm_srai_epi16(gws, 4); - - // back to byte, set up for transpose - __m128i brb = _mm_packus_epi16(rw, bw); - __m128i gxb = _mm_packus_epi16(gw, xw); - - // transpose to interleave channels - __m128i t0 = _mm_unpacklo_epi8(brb, gxb); - __m128i t1 = _mm_unpackhi_epi8(brb, gxb); - __m128i o0 = _mm_unpacklo_epi16(t0, t1); - __m128i o1 = _mm_unpackhi_epi16(t0, t1); - - // store - _mm_storeu_si128((__m128i *) (out + 0), o0); - _mm_storeu_si128((__m128i *) (out + 16), o1); - out += 32; - } - } -#endif - -#ifdef STBI_NEON - // in this version, step=3 support would be easy to add. but is there demand? - if (step == 4) { - // this is a fairly straightforward implementation and not super-optimized. - uint8x8_t signflip = vdup_n_u8(0x80); - int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); - int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); - int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); - int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); - - for (; i+7 < count; i += 8) { - // load - uint8x8_t y_bytes = vld1_u8(y + i); - uint8x8_t cr_bytes = vld1_u8(pcr + i); - uint8x8_t cb_bytes = vld1_u8(pcb + i); - int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); - int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); - - // expand to s16 - int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); - int16x8_t crw = vshll_n_s8(cr_biased, 7); - int16x8_t cbw = vshll_n_s8(cb_biased, 7); - - // color transform - int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); - int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); - int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); - int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); - int16x8_t rws = vaddq_s16(yws, cr0); - int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); - int16x8_t bws = vaddq_s16(yws, cb1); - - // undo scaling, round, convert to byte - uint8x8x4_t o; - o.val[0] = vqrshrun_n_s16(rws, 4); - o.val[1] = vqrshrun_n_s16(gws, 4); - o.val[2] = vqrshrun_n_s16(bws, 4); - o.val[3] = vdup_n_u8(255); - - // store, interleaving r/g/b/a - vst4_u8(out, o); - out += 8*4; - } - } -#endif - - for (; i < count; ++i) { - int y_fixed = (y[i] << 20) + (1<<19); // rounding - int r,g,b; - int cr = pcr[i] - 128; - int cb = pcb[i] - 128; - r = y_fixed + cr* stbi__float2fixed(1.40200f); - g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); - b = y_fixed + cb* stbi__float2fixed(1.77200f); - r >>= 20; - g >>= 20; - b >>= 20; - if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } - if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } - if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } - out[0] = (stbi_uc)r; - out[1] = (stbi_uc)g; - out[2] = (stbi_uc)b; - out[3] = 255; - out += step; - } -} -#endif - -// set up the kernels -static void stbi__setup_jpeg(stbi__jpeg *j) -{ - j->idct_block_kernel = stbi__idct_block; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; - -#ifdef STBI_SSE2 - if (stbi__sse2_available()) { - j->idct_block_kernel = stbi__idct_simd; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; - } -#endif - -#ifdef STBI_NEON - j->idct_block_kernel = stbi__idct_simd; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; -#endif -} - -// clean up the temporary component buffers -static void stbi__cleanup_jpeg(stbi__jpeg *j) -{ - stbi__free_jpeg_components(j, j->s->img_n, 0); -} - -typedef struct -{ - resample_row_func resample; - stbi_uc *line0,*line1; - int hs,vs; // expansion factor in each axis - int w_lores; // horizontal pixels pre-expansion - int ystep; // how far through vertical expansion we are - int ypos; // which pre-expansion row we're on -} stbi__resample; - -// fast 0..255 * 0..255 => 0..255 rounded multiplication -static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) -{ - unsigned int t = x*y + 128; - return (stbi_uc) ((t + (t >>8)) >> 8); -} - -static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) -{ - int n, decode_n, is_rgb; - z->s->img_n = 0; // make stbi__cleanup_jpeg safe - - // validate req_comp - if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - - // load a jpeg image from whichever source, but leave in YCbCr format - if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } - - // determine actual number of components to generate - n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; - - is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); - - if (z->s->img_n == 3 && n < 3 && !is_rgb) - decode_n = 1; - else - decode_n = z->s->img_n; - - // resample and color-convert - { - int k; - unsigned int i,j; - stbi_uc *output; - stbi_uc *coutput[4]; - - stbi__resample res_comp[4]; - - for (k=0; k < decode_n; ++k) { - stbi__resample *r = &res_comp[k]; - - // allocate line buffer big enough for upsampling off the edges - // with upsample factor of 4 - z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); - if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } - - r->hs = z->img_h_max / z->img_comp[k].h; - r->vs = z->img_v_max / z->img_comp[k].v; - r->ystep = r->vs >> 1; - r->w_lores = (z->s->img_x + r->hs-1) / r->hs; - r->ypos = 0; - r->line0 = r->line1 = z->img_comp[k].data; - - if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; - else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; - else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; - else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; - else r->resample = stbi__resample_row_generic; - } - - // can't error after this so, this is safe - output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); - if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } - - // now go ahead and resample - for (j=0; j < z->s->img_y; ++j) { - stbi_uc *out = output + n * z->s->img_x * j; - for (k=0; k < decode_n; ++k) { - stbi__resample *r = &res_comp[k]; - int y_bot = r->ystep >= (r->vs >> 1); - coutput[k] = r->resample(z->img_comp[k].linebuf, - y_bot ? r->line1 : r->line0, - y_bot ? r->line0 : r->line1, - r->w_lores, r->hs); - if (++r->ystep >= r->vs) { - r->ystep = 0; - r->line0 = r->line1; - if (++r->ypos < z->img_comp[k].y) - r->line1 += z->img_comp[k].w2; - } - } - if (n >= 3) { - stbi_uc *y = coutput[0]; - if (z->s->img_n == 3) { - if (is_rgb) { - for (i=0; i < z->s->img_x; ++i) { - out[0] = y[i]; - out[1] = coutput[1][i]; - out[2] = coutput[2][i]; - out[3] = 255; - out += n; - } - } else { - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - } - } else if (z->s->img_n == 4) { - if (z->app14_color_transform == 0) { // CMYK - for (i=0; i < z->s->img_x; ++i) { - stbi_uc m = coutput[3][i]; - out[0] = stbi__blinn_8x8(coutput[0][i], m); - out[1] = stbi__blinn_8x8(coutput[1][i], m); - out[2] = stbi__blinn_8x8(coutput[2][i], m); - out[3] = 255; - out += n; - } - } else if (z->app14_color_transform == 2) { // YCCK - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - for (i=0; i < z->s->img_x; ++i) { - stbi_uc m = coutput[3][i]; - out[0] = stbi__blinn_8x8(255 - out[0], m); - out[1] = stbi__blinn_8x8(255 - out[1], m); - out[2] = stbi__blinn_8x8(255 - out[2], m); - out += n; - } - } else { // YCbCr + alpha? Ignore the fourth channel for now - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - } - } else - for (i=0; i < z->s->img_x; ++i) { - out[0] = out[1] = out[2] = y[i]; - out[3] = 255; // not used if n==3 - out += n; - } - } else { - if (is_rgb) { - if (n == 1) - for (i=0; i < z->s->img_x; ++i) - *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); - else { - for (i=0; i < z->s->img_x; ++i, out += 2) { - out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); - out[1] = 255; - } - } - } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { - for (i=0; i < z->s->img_x; ++i) { - stbi_uc m = coutput[3][i]; - stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); - stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); - stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); - out[0] = stbi__compute_y(r, g, b); - out[1] = 255; - out += n; - } - } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { - for (i=0; i < z->s->img_x; ++i) { - out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); - out[1] = 255; - out += n; - } - } else { - stbi_uc *y = coutput[0]; - if (n == 1) - for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; - else - for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; - } - } - } - stbi__cleanup_jpeg(z); - *out_x = z->s->img_x; - *out_y = z->s->img_y; - if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output - return output; - } -} - -static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - unsigned char* result; - stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); - STBI_NOTUSED(ri); - j->s = s; - stbi__setup_jpeg(j); - result = load_jpeg_image(j, x,y,comp,req_comp); - STBI_FREE(j); - return result; -} - -static int stbi__jpeg_test(stbi__context *s) -{ - int r; - stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); - j->s = s; - stbi__setup_jpeg(j); - r = stbi__decode_jpeg_header(j, STBI__SCAN_type); - stbi__rewind(s); - STBI_FREE(j); - return r; -} - -static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) -{ - if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { - stbi__rewind( j->s ); - return 0; - } - if (x) *x = j->s->img_x; - if (y) *y = j->s->img_y; - if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; - return 1; -} - -static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) -{ - int result; - stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); - j->s = s; - result = stbi__jpeg_info_raw(j, x, y, comp); - STBI_FREE(j); - return result; -} -#endif - -// public domain zlib decode v0.2 Sean Barrett 2006-11-18 -// simple implementation -// - all input must be provided in an upfront buffer -// - all output is written to a single output buffer (can malloc/realloc) -// performance -// - fast huffman - -#ifndef STBI_NO_ZLIB - -// fast-way is faster to check than jpeg huffman, but slow way is slower -#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables -#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) - -// zlib-style huffman encoding -// (jpegs packs from left, zlib from right, so can't share code) -typedef struct -{ - stbi__uint16 fast[1 << STBI__ZFAST_BITS]; - stbi__uint16 firstcode[16]; - int maxcode[17]; - stbi__uint16 firstsymbol[16]; - stbi_uc size[288]; - stbi__uint16 value[288]; -} stbi__zhuffman; - -stbi_inline static int stbi__bitreverse16(int n) -{ - n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); - n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); - n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); - n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); - return n; -} - -stbi_inline static int stbi__bit_reverse(int v, int bits) -{ - STBI_ASSERT(bits <= 16); - // to bit reverse n bits, reverse 16 and shift - // e.g. 11 bits, bit reverse and shift away 5 - return stbi__bitreverse16(v) >> (16-bits); -} - -static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) -{ - int i,k=0; - int code, next_code[16], sizes[17]; - - // DEFLATE spec for generating codes - memset(sizes, 0, sizeof(sizes)); - memset(z->fast, 0, sizeof(z->fast)); - for (i=0; i < num; ++i) - ++sizes[sizelist[i]]; - sizes[0] = 0; - for (i=1; i < 16; ++i) - if (sizes[i] > (1 << i)) - return stbi__err("bad sizes", "Corrupt PNG"); - code = 0; - for (i=1; i < 16; ++i) { - next_code[i] = code; - z->firstcode[i] = (stbi__uint16) code; - z->firstsymbol[i] = (stbi__uint16) k; - code = (code + sizes[i]); - if (sizes[i]) - if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); - z->maxcode[i] = code << (16-i); // preshift for inner loop - code <<= 1; - k += sizes[i]; - } - z->maxcode[16] = 0x10000; // sentinel - for (i=0; i < num; ++i) { - int s = sizelist[i]; - if (s) { - int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; - stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); - z->size [c] = (stbi_uc ) s; - z->value[c] = (stbi__uint16) i; - if (s <= STBI__ZFAST_BITS) { - int j = stbi__bit_reverse(next_code[s],s); - while (j < (1 << STBI__ZFAST_BITS)) { - z->fast[j] = fastv; - j += (1 << s); - } - } - ++next_code[s]; - } - } - return 1; -} - -// zlib-from-memory implementation for PNG reading -// because PNG allows splitting the zlib stream arbitrarily, -// and it's annoying structurally to have PNG call ZLIB call PNG, -// we require PNG read all the IDATs and combine them into a single -// memory buffer - -typedef struct -{ - stbi_uc *zbuffer, *zbuffer_end; - int num_bits; - stbi__uint32 code_buffer; - - char *zout; - char *zout_start; - char *zout_end; - int z_expandable; - - stbi__zhuffman z_length, z_distance; -} stbi__zbuf; - -stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) -{ - if (z->zbuffer >= z->zbuffer_end) return 0; - return *z->zbuffer++; -} - -static void stbi__fill_bits(stbi__zbuf *z) -{ - do { - STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); - z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; - z->num_bits += 8; - } while (z->num_bits <= 24); -} - -stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) -{ - unsigned int k; - if (z->num_bits < n) stbi__fill_bits(z); - k = z->code_buffer & ((1 << n) - 1); - z->code_buffer >>= n; - z->num_bits -= n; - return k; -} - -static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) -{ - int b,s,k; - // not resolved by fast table, so compute it the slow way - // use jpeg approach, which requires MSbits at top - k = stbi__bit_reverse(a->code_buffer, 16); - for (s=STBI__ZFAST_BITS+1; ; ++s) - if (k < z->maxcode[s]) - break; - if (s == 16) return -1; // invalid code! - // code size is s, so: - b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; - STBI_ASSERT(z->size[b] == s); - a->code_buffer >>= s; - a->num_bits -= s; - return z->value[b]; -} - -stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) -{ - int b,s; - if (a->num_bits < 16) stbi__fill_bits(a); - b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; - if (b) { - s = b >> 9; - a->code_buffer >>= s; - a->num_bits -= s; - return b & 511; - } - return stbi__zhuffman_decode_slowpath(a, z); -} - -static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes -{ - char *q; - int cur, limit, old_limit; - z->zout = zout; - if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); - cur = (int) (z->zout - z->zout_start); - limit = old_limit = (int) (z->zout_end - z->zout_start); - while (cur + n > limit) - limit *= 2; - q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); - STBI_NOTUSED(old_limit); - if (q == NULL) return stbi__err("outofmem", "Out of memory"); - z->zout_start = q; - z->zout = q + cur; - z->zout_end = q + limit; - return 1; -} - -static const int stbi__zlength_base[31] = { - 3,4,5,6,7,8,9,10,11,13, - 15,17,19,23,27,31,35,43,51,59, - 67,83,99,115,131,163,195,227,258,0,0 }; - -static const int stbi__zlength_extra[31]= -{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; - -static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, -257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; - -static const int stbi__zdist_extra[32] = -{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; - -static int stbi__parse_huffman_block(stbi__zbuf *a) -{ - char *zout = a->zout; - for(;;) { - int z = stbi__zhuffman_decode(a, &a->z_length); - if (z < 256) { - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes - if (zout >= a->zout_end) { - if (!stbi__zexpand(a, zout, 1)) return 0; - zout = a->zout; - } - *zout++ = (char) z; - } else { - stbi_uc *p; - int len,dist; - if (z == 256) { - a->zout = zout; - return 1; - } - z -= 257; - len = stbi__zlength_base[z]; - if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); - z = stbi__zhuffman_decode(a, &a->z_distance); - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); - dist = stbi__zdist_base[z]; - if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); - if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); - if (zout + len > a->zout_end) { - if (!stbi__zexpand(a, zout, len)) return 0; - zout = a->zout; - } - p = (stbi_uc *) (zout - dist); - if (dist == 1) { // run of one byte; common in images. - stbi_uc v = *p; - if (len) { do *zout++ = v; while (--len); } - } else { - if (len) { do *zout++ = *p++; while (--len); } - } - } - } -} - -static int stbi__compute_huffman_codes(stbi__zbuf *a) -{ - static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; - stbi__zhuffman z_codelength; - stbi_uc lencodes[286+32+137];//padding for maximum single op - stbi_uc codelength_sizes[19]; - int i,n; - - int hlit = stbi__zreceive(a,5) + 257; - int hdist = stbi__zreceive(a,5) + 1; - int hclen = stbi__zreceive(a,4) + 4; - int ntot = hlit + hdist; - - memset(codelength_sizes, 0, sizeof(codelength_sizes)); - for (i=0; i < hclen; ++i) { - int s = stbi__zreceive(a,3); - codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; - } - if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; - - n = 0; - while (n < ntot) { - int c = stbi__zhuffman_decode(a, &z_codelength); - if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); - if (c < 16) - lencodes[n++] = (stbi_uc) c; - else { - stbi_uc fill = 0; - if (c == 16) { - c = stbi__zreceive(a,2)+3; - if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); - fill = lencodes[n-1]; - } else if (c == 17) - c = stbi__zreceive(a,3)+3; - else { - STBI_ASSERT(c == 18); - c = stbi__zreceive(a,7)+11; - } - if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); - memset(lencodes+n, fill, c); - n += c; - } - } - if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); - if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; - if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; - return 1; -} - -static int stbi__parse_uncompressed_block(stbi__zbuf *a) -{ - stbi_uc header[4]; - int len,nlen,k; - if (a->num_bits & 7) - stbi__zreceive(a, a->num_bits & 7); // discard - // drain the bit-packed data into header - k = 0; - while (a->num_bits > 0) { - header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check - a->code_buffer >>= 8; - a->num_bits -= 8; - } - STBI_ASSERT(a->num_bits == 0); - // now fill header the normal way - while (k < 4) - header[k++] = stbi__zget8(a); - len = header[1] * 256 + header[0]; - nlen = header[3] * 256 + header[2]; - if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); - if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); - if (a->zout + len > a->zout_end) - if (!stbi__zexpand(a, a->zout, len)) return 0; - memcpy(a->zout, a->zbuffer, len); - a->zbuffer += len; - a->zout += len; - return 1; -} - -static int stbi__parse_zlib_header(stbi__zbuf *a) -{ - int cmf = stbi__zget8(a); - int cm = cmf & 15; - /* int cinfo = cmf >> 4; */ - int flg = stbi__zget8(a); - if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec - if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png - if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png - // window = 1 << (8 + cinfo)... but who cares, we fully buffer output - return 1; -} - -static const stbi_uc stbi__zdefault_length[288] = -{ - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 -}; -static const stbi_uc stbi__zdefault_distance[32] = -{ - 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 -}; -/* -Init algorithm: -{ - int i; // use <= to match clearly with spec - for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; - for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; - for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; - for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; - - for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; -} -*/ - -static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) -{ - int final, type; - if (parse_header) - if (!stbi__parse_zlib_header(a)) return 0; - a->num_bits = 0; - a->code_buffer = 0; - do { - final = stbi__zreceive(a,1); - type = stbi__zreceive(a,2); - if (type == 0) { - if (!stbi__parse_uncompressed_block(a)) return 0; - } else if (type == 3) { - return 0; - } else { - if (type == 1) { - // use fixed code lengths - if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; - if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; - } else { - if (!stbi__compute_huffman_codes(a)) return 0; - } - if (!stbi__parse_huffman_block(a)) return 0; - } - } while (!final); - return 1; -} - -static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) -{ - a->zout_start = obuf; - a->zout = obuf; - a->zout_end = obuf + olen; - a->z_expandable = exp; - - return stbi__parse_zlib(a, parse_header); -} - -STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(initial_size); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer + len; - if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) -{ - return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); -} - -STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(initial_size); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer + len; - if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) -{ - stbi__zbuf a; - a.zbuffer = (stbi_uc *) ibuffer; - a.zbuffer_end = (stbi_uc *) ibuffer + ilen; - if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) - return (int) (a.zout - a.zout_start); - else - return -1; -} - -STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(16384); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer+len; - if (stbi__do_zlib(&a, p, 16384, 1, 0)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) -{ - stbi__zbuf a; - a.zbuffer = (stbi_uc *) ibuffer; - a.zbuffer_end = (stbi_uc *) ibuffer + ilen; - if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) - return (int) (a.zout - a.zout_start); - else - return -1; -} -#endif - -// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 -// simple implementation -// - only 8-bit samples -// - no CRC checking -// - allocates lots of intermediate memory -// - avoids problem of streaming data between subsystems -// - avoids explicit window management -// performance -// - uses stb_zlib, a PD zlib implementation with fast huffman decoding - -#ifndef STBI_NO_PNG -typedef struct -{ - stbi__uint32 length; - stbi__uint32 type; -} stbi__pngchunk; - -static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) -{ - stbi__pngchunk c; - c.length = stbi__get32be(s); - c.type = stbi__get32be(s); - return c; -} - -static int stbi__check_png_header(stbi__context *s) -{ - static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; - int i; - for (i=0; i < 8; ++i) - if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); - return 1; -} - -typedef struct -{ - stbi__context *s; - stbi_uc *idata, *expanded, *out; - int depth; -} stbi__png; - - -enum { - STBI__F_none=0, - STBI__F_sub=1, - STBI__F_up=2, - STBI__F_avg=3, - STBI__F_paeth=4, - // synthetic filters used for first scanline to avoid needing a dummy row of 0s - STBI__F_avg_first, - STBI__F_paeth_first -}; - -static stbi_uc first_row_filter[5] = -{ - STBI__F_none, - STBI__F_sub, - STBI__F_none, - STBI__F_avg_first, - STBI__F_paeth_first -}; - -static int stbi__paeth(int a, int b, int c) -{ - int p = a + b - c; - int pa = abs(p-a); - int pb = abs(p-b); - int pc = abs(p-c); - if (pa <= pb && pa <= pc) return a; - if (pb <= pc) return b; - return c; -} - -static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; - -// create the png data from post-deflated data -static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) -{ - int bytes = (depth == 16? 2 : 1); - stbi__context *s = a->s; - stbi__uint32 i,j,stride = x*out_n*bytes; - stbi__uint32 img_len, img_width_bytes; - int k; - int img_n = s->img_n; // copy it into a local for later - - int output_bytes = out_n*bytes; - int filter_bytes = img_n*bytes; - int width = x; - - STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); - a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into - if (!a->out) return stbi__err("outofmem", "Out of memory"); - - if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); - img_width_bytes = (((img_n * x * depth) + 7) >> 3); - img_len = (img_width_bytes + 1) * y; - - // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, - // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), - // so just check for raw_len < img_len always. - if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); - - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *prior; - int filter = *raw++; - - if (filter > 4) - return stbi__err("invalid filter","Corrupt PNG"); - - if (depth < 8) { - STBI_ASSERT(img_width_bytes <= x); - cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place - filter_bytes = 1; - width = img_width_bytes; - } - prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above - - // if first row, use special filter that doesn't sample previous row - if (j == 0) filter = first_row_filter[filter]; - - // handle first byte explicitly - for (k=0; k < filter_bytes; ++k) { - switch (filter) { - case STBI__F_none : cur[k] = raw[k]; break; - case STBI__F_sub : cur[k] = raw[k]; break; - case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; - case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; - case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; - case STBI__F_avg_first : cur[k] = raw[k]; break; - case STBI__F_paeth_first: cur[k] = raw[k]; break; - } - } - - if (depth == 8) { - if (img_n != out_n) - cur[img_n] = 255; // first pixel - raw += img_n; - cur += out_n; - prior += out_n; - } else if (depth == 16) { - if (img_n != out_n) { - cur[filter_bytes] = 255; // first pixel top byte - cur[filter_bytes+1] = 255; // first pixel bottom byte - } - raw += filter_bytes; - cur += output_bytes; - prior += output_bytes; - } else { - raw += 1; - cur += 1; - prior += 1; - } - - // this is a little gross, so that we don't switch per-pixel or per-component - if (depth < 8 || img_n == out_n) { - int nk = (width - 1)*filter_bytes; - #define STBI__CASE(f) \ - case f: \ - for (k=0; k < nk; ++k) - switch (filter) { - // "none" filter turns into a memcpy here; make that explicit. - case STBI__F_none: memcpy(cur, raw, nk); break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; - } - #undef STBI__CASE - raw += nk; - } else { - STBI_ASSERT(img_n+1 == out_n); - #define STBI__CASE(f) \ - case f: \ - for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ - for (k=0; k < filter_bytes; ++k) - switch (filter) { - STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; - } - #undef STBI__CASE - - // the loop above sets the high byte of the pixels' alpha, but for - // 16 bit png files we also need the low byte set. we'll do that here. - if (depth == 16) { - cur = a->out + stride*j; // start at the beginning of the row again - for (i=0; i < x; ++i,cur+=output_bytes) { - cur[filter_bytes+1] = 255; - } - } - } - } - - // we make a separate pass to expand bits to pixels; for performance, - // this could run two scanlines behind the above code, so it won't - // intefere with filtering but will still be in the cache. - if (depth < 8) { - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; - // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit - // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop - stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range - - // note that the final byte might overshoot and write more data than desired. - // we can allocate enough data that this never writes out of memory, but it - // could also overwrite the next scanline. can it overwrite non-empty data - // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. - // so we need to explicitly clamp the final ones - - if (depth == 4) { - for (k=x*img_n; k >= 2; k-=2, ++in) { - *cur++ = scale * ((*in >> 4) ); - *cur++ = scale * ((*in ) & 0x0f); - } - if (k > 0) *cur++ = scale * ((*in >> 4) ); - } else if (depth == 2) { - for (k=x*img_n; k >= 4; k-=4, ++in) { - *cur++ = scale * ((*in >> 6) ); - *cur++ = scale * ((*in >> 4) & 0x03); - *cur++ = scale * ((*in >> 2) & 0x03); - *cur++ = scale * ((*in ) & 0x03); - } - if (k > 0) *cur++ = scale * ((*in >> 6) ); - if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); - if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); - } else if (depth == 1) { - for (k=x*img_n; k >= 8; k-=8, ++in) { - *cur++ = scale * ((*in >> 7) ); - *cur++ = scale * ((*in >> 6) & 0x01); - *cur++ = scale * ((*in >> 5) & 0x01); - *cur++ = scale * ((*in >> 4) & 0x01); - *cur++ = scale * ((*in >> 3) & 0x01); - *cur++ = scale * ((*in >> 2) & 0x01); - *cur++ = scale * ((*in >> 1) & 0x01); - *cur++ = scale * ((*in ) & 0x01); - } - if (k > 0) *cur++ = scale * ((*in >> 7) ); - if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); - if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); - if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); - if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); - if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); - if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); - } - if (img_n != out_n) { - int q; - // insert alpha = 255 - cur = a->out + stride*j; - if (img_n == 1) { - for (q=x-1; q >= 0; --q) { - cur[q*2+1] = 255; - cur[q*2+0] = cur[q]; - } - } else { - STBI_ASSERT(img_n == 3); - for (q=x-1; q >= 0; --q) { - cur[q*4+3] = 255; - cur[q*4+2] = cur[q*3+2]; - cur[q*4+1] = cur[q*3+1]; - cur[q*4+0] = cur[q*3+0]; - } - } - } - } - } else if (depth == 16) { - // force the image data from big-endian to platform-native. - // this is done in a separate pass due to the decoding relying - // on the data being untouched, but could probably be done - // per-line during decode if care is taken. - stbi_uc *cur = a->out; - stbi__uint16 *cur16 = (stbi__uint16*)cur; - - for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { - *cur16 = (cur[0] << 8) | cur[1]; - } - } - - return 1; -} - -static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) -{ - int bytes = (depth == 16 ? 2 : 1); - int out_bytes = out_n * bytes; - stbi_uc *final; - int p; - if (!interlaced) - return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); - - // de-interlacing - final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); - for (p=0; p < 7; ++p) { - int xorig[] = { 0,4,0,2,0,1,0 }; - int yorig[] = { 0,0,4,0,2,0,1 }; - int xspc[] = { 8,8,4,4,2,2,1 }; - int yspc[] = { 8,8,8,4,4,2,2 }; - int i,j,x,y; - // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 - x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; - y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; - if (x && y) { - stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; - if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { - STBI_FREE(final); - return 0; - } - for (j=0; j < y; ++j) { - for (i=0; i < x; ++i) { - int out_y = j*yspc[p]+yorig[p]; - int out_x = i*xspc[p]+xorig[p]; - memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, - a->out + (j*x+i)*out_bytes, out_bytes); - } - } - STBI_FREE(a->out); - image_data += img_len; - image_data_len -= img_len; - } - } - a->out = final; - - return 1; -} - -static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi_uc *p = z->out; - - // compute color-based transparency, assuming we've - // already got 255 as the alpha value in the output - STBI_ASSERT(out_n == 2 || out_n == 4); - - if (out_n == 2) { - for (i=0; i < pixel_count; ++i) { - p[1] = (p[0] == tc[0] ? 0 : 255); - p += 2; - } - } else { - for (i=0; i < pixel_count; ++i) { - if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) - p[3] = 0; - p += 4; - } - } - return 1; -} - -static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi__uint16 *p = (stbi__uint16*) z->out; - - // compute color-based transparency, assuming we've - // already got 65535 as the alpha value in the output - STBI_ASSERT(out_n == 2 || out_n == 4); - - if (out_n == 2) { - for (i = 0; i < pixel_count; ++i) { - p[1] = (p[0] == tc[0] ? 0 : 65535); - p += 2; - } - } else { - for (i = 0; i < pixel_count; ++i) { - if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) - p[3] = 0; - p += 4; - } - } - return 1; -} - -static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) -{ - stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; - stbi_uc *p, *temp_out, *orig = a->out; - - p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); - if (p == NULL) return stbi__err("outofmem", "Out of memory"); - - // between here and free(out) below, exitting would leak - temp_out = p; - - if (pal_img_n == 3) { - for (i=0; i < pixel_count; ++i) { - int n = orig[i]*4; - p[0] = palette[n ]; - p[1] = palette[n+1]; - p[2] = palette[n+2]; - p += 3; - } - } else { - for (i=0; i < pixel_count; ++i) { - int n = orig[i]*4; - p[0] = palette[n ]; - p[1] = palette[n+1]; - p[2] = palette[n+2]; - p[3] = palette[n+3]; - p += 4; - } - } - STBI_FREE(a->out); - a->out = temp_out; - - STBI_NOTUSED(len); - - return 1; -} - -static int stbi__unpremultiply_on_load = 0; -static int stbi__de_iphone_flag = 0; - -STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) -{ - stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; -} - -STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) -{ - stbi__de_iphone_flag = flag_true_if_should_convert; -} - -static void stbi__de_iphone(stbi__png *z) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi_uc *p = z->out; - - if (s->img_out_n == 3) { // convert bgr to rgb - for (i=0; i < pixel_count; ++i) { - stbi_uc t = p[0]; - p[0] = p[2]; - p[2] = t; - p += 3; - } - } else { - STBI_ASSERT(s->img_out_n == 4); - if (stbi__unpremultiply_on_load) { - // convert bgr to rgb and unpremultiply - for (i=0; i < pixel_count; ++i) { - stbi_uc a = p[3]; - stbi_uc t = p[0]; - if (a) { - stbi_uc half = a / 2; - p[0] = (p[2] * 255 + half) / a; - p[1] = (p[1] * 255 + half) / a; - p[2] = ( t * 255 + half) / a; - } else { - p[0] = p[2]; - p[2] = t; - } - p += 4; - } - } else { - // convert bgr to rgb - for (i=0; i < pixel_count; ++i) { - stbi_uc t = p[0]; - p[0] = p[2]; - p[2] = t; - p += 4; - } - } - } -} - -#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) - -static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) -{ - stbi_uc palette[1024], pal_img_n=0; - stbi_uc has_trans=0, tc[3]; - stbi__uint16 tc16[3]; - stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; - int first=1,k,interlace=0, color=0, is_iphone=0; - stbi__context *s = z->s; - - z->expanded = NULL; - z->idata = NULL; - z->out = NULL; - - if (!stbi__check_png_header(s)) return 0; - - if (scan == STBI__SCAN_type) return 1; - - for (;;) { - stbi__pngchunk c = stbi__get_chunk_header(s); - switch (c.type) { - case STBI__PNG_TYPE('C','g','B','I'): - is_iphone = 1; - stbi__skip(s, c.length); - break; - case STBI__PNG_TYPE('I','H','D','R'): { - int comp,filter; - if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); - first = 0; - if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); - s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); - s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); - z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); - color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); - if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); - if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); - comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); - filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); - interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); - if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); - if (!pal_img_n) { - s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); - if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); - if (scan == STBI__SCAN_header) return 1; - } else { - // if paletted, then pal_n is our final components, and - // img_n is # components to decompress/filter. - s->img_n = 1; - if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); - // if SCAN_header, have to scan to see if we have a tRNS - } - break; - } - - case STBI__PNG_TYPE('P','L','T','E'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); - pal_len = c.length / 3; - if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); - for (i=0; i < pal_len; ++i) { - palette[i*4+0] = stbi__get8(s); - palette[i*4+1] = stbi__get8(s); - palette[i*4+2] = stbi__get8(s); - palette[i*4+3] = 255; - } - break; - } - - case STBI__PNG_TYPE('t','R','N','S'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); - if (pal_img_n) { - if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } - if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); - if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); - pal_img_n = 4; - for (i=0; i < c.length; ++i) - palette[i*4+3] = stbi__get8(s); - } else { - if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); - if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); - has_trans = 1; - if (z->depth == 16) { - for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is - } else { - for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger - } - } - break; - } - - case STBI__PNG_TYPE('I','D','A','T'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); - if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } - if ((int)(ioff + c.length) < (int)ioff) return 0; - if (ioff + c.length > idata_limit) { - stbi__uint32 idata_limit_old = idata_limit; - stbi_uc *p; - if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; - while (ioff + c.length > idata_limit) - idata_limit *= 2; - STBI_NOTUSED(idata_limit_old); - p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); - z->idata = p; - } - if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); - ioff += c.length; - break; - } - - case STBI__PNG_TYPE('I','E','N','D'): { - stbi__uint32 raw_len, bpl; - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (scan != STBI__SCAN_load) return 1; - if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); - // initial guess for decoded data size to avoid unnecessary reallocs - bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component - raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; - z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); - if (z->expanded == NULL) return 0; // zlib should set error - STBI_FREE(z->idata); z->idata = NULL; - if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) - s->img_out_n = s->img_n+1; - else - s->img_out_n = s->img_n; - if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; - if (has_trans) { - if (z->depth == 16) { - if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; - } else { - if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; - } - } - if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) - stbi__de_iphone(z); - if (pal_img_n) { - // pal_img_n == 3 or 4 - s->img_n = pal_img_n; // record the actual colors we had - s->img_out_n = pal_img_n; - if (req_comp >= 3) s->img_out_n = req_comp; - if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) - return 0; - } else if (has_trans) { - // non-paletted image with tRNS -> source image has (constant) alpha - ++s->img_n; - } - STBI_FREE(z->expanded); z->expanded = NULL; - return 1; - } - - default: - // if critical, fail - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if ((c.type & (1 << 29)) == 0) { - #ifndef STBI_NO_FAILURE_STRINGS - // not threadsafe - static char invalid_chunk[] = "XXXX PNG chunk not known"; - invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); - invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); - invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); - invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); - #endif - return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); - } - stbi__skip(s, c.length); - break; - } - // end of PNG chunk, read and skip CRC - stbi__get32be(s); - } -} - -static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) -{ - void *result=NULL; - if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { - if (p->depth < 8) - ri->bits_per_channel = 8; - else - ri->bits_per_channel = p->depth; - result = p->out; - p->out = NULL; - if (req_comp && req_comp != p->s->img_out_n) { - if (ri->bits_per_channel == 8) - result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); - else - result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); - p->s->img_out_n = req_comp; - if (result == NULL) return result; - } - *x = p->s->img_x; - *y = p->s->img_y; - if (n) *n = p->s->img_n; - } - STBI_FREE(p->out); p->out = NULL; - STBI_FREE(p->expanded); p->expanded = NULL; - STBI_FREE(p->idata); p->idata = NULL; - - return result; -} - -static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi__png p; - p.s = s; - return stbi__do_png(&p, x,y,comp,req_comp, ri); -} - -static int stbi__png_test(stbi__context *s) -{ - int r; - r = stbi__check_png_header(s); - stbi__rewind(s); - return r; -} - -static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) -{ - if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { - stbi__rewind( p->s ); - return 0; - } - if (x) *x = p->s->img_x; - if (y) *y = p->s->img_y; - if (comp) *comp = p->s->img_n; - return 1; -} - -static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) -{ - stbi__png p; - p.s = s; - return stbi__png_info_raw(&p, x, y, comp); -} - -static int stbi__png_is16(stbi__context *s) -{ - stbi__png p; - p.s = s; - if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) - return 0; - if (p.depth != 16) { - stbi__rewind(p.s); - return 0; - } - return 1; -} -#endif - -// Microsoft/Windows BMP image - -#ifndef STBI_NO_BMP -static int stbi__bmp_test_raw(stbi__context *s) -{ - int r; - int sz; - if (stbi__get8(s) != 'B') return 0; - if (stbi__get8(s) != 'M') return 0; - stbi__get32le(s); // discard filesize - stbi__get16le(s); // discard reserved - stbi__get16le(s); // discard reserved - stbi__get32le(s); // discard data offset - sz = stbi__get32le(s); - r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); - return r; -} - -static int stbi__bmp_test(stbi__context *s) -{ - int r = stbi__bmp_test_raw(s); - stbi__rewind(s); - return r; -} - - -// returns 0..31 for the highest set bit -static int stbi__high_bit(unsigned int z) -{ - int n=0; - if (z == 0) return -1; - if (z >= 0x10000) n += 16, z >>= 16; - if (z >= 0x00100) n += 8, z >>= 8; - if (z >= 0x00010) n += 4, z >>= 4; - if (z >= 0x00004) n += 2, z >>= 2; - if (z >= 0x00002) n += 1, z >>= 1; - return n; -} - -static int stbi__bitcount(unsigned int a) -{ - a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 - a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 - a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits - a = (a + (a >> 8)); // max 16 per 8 bits - a = (a + (a >> 16)); // max 32 per 8 bits - return a & 0xff; -} - -// extract an arbitrarily-aligned N-bit value (N=bits) -// from v, and then make it 8-bits long and fractionally -// extend it to full full range. -static int stbi__shiftsigned(int v, int shift, int bits) -{ - static unsigned int mul_table[9] = { - 0, - 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, - 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, - }; - static unsigned int shift_table[9] = { - 0, 0,0,1,0,2,4,6,0, - }; - if (shift < 0) - v <<= -shift; - else - v >>= shift; - STBI_ASSERT(v >= 0 && v < 256); - v >>= (8-bits); - STBI_ASSERT(bits >= 0 && bits <= 8); - return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; -} - -typedef struct -{ - int bpp, offset, hsz; - unsigned int mr,mg,mb,ma, all_a; -} stbi__bmp_data; - -static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) -{ - int hsz; - if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); - stbi__get32le(s); // discard filesize - stbi__get16le(s); // discard reserved - stbi__get16le(s); // discard reserved - info->offset = stbi__get32le(s); - info->hsz = hsz = stbi__get32le(s); - info->mr = info->mg = info->mb = info->ma = 0; - - if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); - if (hsz == 12) { - s->img_x = stbi__get16le(s); - s->img_y = stbi__get16le(s); - } else { - s->img_x = stbi__get32le(s); - s->img_y = stbi__get32le(s); - } - if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); - info->bpp = stbi__get16le(s); - if (hsz != 12) { - int compress = stbi__get32le(s); - if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); - stbi__get32le(s); // discard sizeof - stbi__get32le(s); // discard hres - stbi__get32le(s); // discard vres - stbi__get32le(s); // discard colorsused - stbi__get32le(s); // discard max important - if (hsz == 40 || hsz == 56) { - if (hsz == 56) { - stbi__get32le(s); - stbi__get32le(s); - stbi__get32le(s); - stbi__get32le(s); - } - if (info->bpp == 16 || info->bpp == 32) { - if (compress == 0) { - if (info->bpp == 32) { - info->mr = 0xffu << 16; - info->mg = 0xffu << 8; - info->mb = 0xffu << 0; - info->ma = 0xffu << 24; - info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 - } else { - info->mr = 31u << 10; - info->mg = 31u << 5; - info->mb = 31u << 0; - } - } else if (compress == 3) { - info->mr = stbi__get32le(s); - info->mg = stbi__get32le(s); - info->mb = stbi__get32le(s); - // not documented, but generated by photoshop and handled by mspaint - if (info->mr == info->mg && info->mg == info->mb) { - // ?!?!? - return stbi__errpuc("bad BMP", "bad BMP"); - } - } else - return stbi__errpuc("bad BMP", "bad BMP"); - } - } else { - int i; - if (hsz != 108 && hsz != 124) - return stbi__errpuc("bad BMP", "bad BMP"); - info->mr = stbi__get32le(s); - info->mg = stbi__get32le(s); - info->mb = stbi__get32le(s); - info->ma = stbi__get32le(s); - stbi__get32le(s); // discard color space - for (i=0; i < 12; ++i) - stbi__get32le(s); // discard color space parameters - if (hsz == 124) { - stbi__get32le(s); // discard rendering intent - stbi__get32le(s); // discard offset of profile data - stbi__get32le(s); // discard size of profile data - stbi__get32le(s); // discard reserved - } - } - } - return (void *) 1; -} - - -static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi_uc *out; - unsigned int mr=0,mg=0,mb=0,ma=0, all_a; - stbi_uc pal[256][4]; - int psize=0,i,j,width; - int flip_vertically, pad, target; - stbi__bmp_data info; - STBI_NOTUSED(ri); - - info.all_a = 255; - if (stbi__bmp_parse_header(s, &info) == NULL) - return NULL; // error code already set - - flip_vertically = ((int) s->img_y) > 0; - s->img_y = abs((int) s->img_y); - - mr = info.mr; - mg = info.mg; - mb = info.mb; - ma = info.ma; - all_a = info.all_a; - - if (info.hsz == 12) { - if (info.bpp < 24) - psize = (info.offset - 14 - 24) / 3; - } else { - if (info.bpp < 16) - psize = (info.offset - 14 - info.hsz) >> 2; - } - - s->img_n = ma ? 4 : 3; - if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 - target = req_comp; - else - target = s->img_n; // if they want monochrome, we'll post-convert - - // sanity-check size - if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) - return stbi__errpuc("too large", "Corrupt BMP"); - - out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - if (info.bpp < 16) { - int z=0; - if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } - for (i=0; i < psize; ++i) { - pal[i][2] = stbi__get8(s); - pal[i][1] = stbi__get8(s); - pal[i][0] = stbi__get8(s); - if (info.hsz != 12) stbi__get8(s); - pal[i][3] = 255; - } - stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); - if (info.bpp == 1) width = (s->img_x + 7) >> 3; - else if (info.bpp == 4) width = (s->img_x + 1) >> 1; - else if (info.bpp == 8) width = s->img_x; - else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } - pad = (-width)&3; - if (info.bpp == 1) { - for (j=0; j < (int) s->img_y; ++j) { - int bit_offset = 7, v = stbi__get8(s); - for (i=0; i < (int) s->img_x; ++i) { - int color = (v>>bit_offset)&0x1; - out[z++] = pal[color][0]; - out[z++] = pal[color][1]; - out[z++] = pal[color][2]; - if((--bit_offset) < 0) { - bit_offset = 7; - v = stbi__get8(s); - } - } - stbi__skip(s, pad); - } - } else { - for (j=0; j < (int) s->img_y; ++j) { - for (i=0; i < (int) s->img_x; i += 2) { - int v=stbi__get8(s),v2=0; - if (info.bpp == 4) { - v2 = v & 15; - v >>= 4; - } - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - if (i+1 == (int) s->img_x) break; - v = (info.bpp == 8) ? stbi__get8(s) : v2; - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - } - stbi__skip(s, pad); - } - } - } else { - int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; - int z = 0; - int easy=0; - stbi__skip(s, info.offset - 14 - info.hsz); - if (info.bpp == 24) width = 3 * s->img_x; - else if (info.bpp == 16) width = 2*s->img_x; - else /* bpp = 32 and pad = 0 */ width=0; - pad = (-width) & 3; - if (info.bpp == 24) { - easy = 1; - } else if (info.bpp == 32) { - if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) - easy = 2; - } - if (!easy) { - if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } - // right shift amt to put high bit in position #7 - rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); - gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); - bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); - ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); - } - for (j=0; j < (int) s->img_y; ++j) { - if (easy) { - for (i=0; i < (int) s->img_x; ++i) { - unsigned char a; - out[z+2] = stbi__get8(s); - out[z+1] = stbi__get8(s); - out[z+0] = stbi__get8(s); - z += 3; - a = (easy == 2 ? stbi__get8(s) : 255); - all_a |= a; - if (target == 4) out[z++] = a; - } - } else { - int bpp = info.bpp; - for (i=0; i < (int) s->img_x; ++i) { - stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); - unsigned int a; - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); - a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); - all_a |= a; - if (target == 4) out[z++] = STBI__BYTECAST(a); - } - } - stbi__skip(s, pad); - } - } - - // if alpha channel is all 0s, replace with all 255s - if (target == 4 && all_a == 0) - for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) - out[i] = 255; - - if (flip_vertically) { - stbi_uc t; - for (j=0; j < (int) s->img_y>>1; ++j) { - stbi_uc *p1 = out + j *s->img_x*target; - stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; - for (i=0; i < (int) s->img_x*target; ++i) { - t = p1[i], p1[i] = p2[i], p2[i] = t; - } - } - } - - if (req_comp && req_comp != target) { - out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - - *x = s->img_x; - *y = s->img_y; - if (comp) *comp = s->img_n; - return out; -} -#endif - -// Targa Truevision - TGA -// by Jonathan Dummer -#ifndef STBI_NO_TGA -// returns STBI_rgb or whatever, 0 on error -static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) -{ - // only RGB or RGBA (incl. 16bit) or grey allowed - if (is_rgb16) *is_rgb16 = 0; - switch(bits_per_pixel) { - case 8: return STBI_grey; - case 16: if(is_grey) return STBI_grey_alpha; - // fallthrough - case 15: if(is_rgb16) *is_rgb16 = 1; - return STBI_rgb; - case 24: // fallthrough - case 32: return bits_per_pixel/8; - default: return 0; - } -} - -static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) -{ - int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; - int sz, tga_colormap_type; - stbi__get8(s); // discard Offset - tga_colormap_type = stbi__get8(s); // colormap type - if( tga_colormap_type > 1 ) { - stbi__rewind(s); - return 0; // only RGB or indexed allowed - } - tga_image_type = stbi__get8(s); // image type - if ( tga_colormap_type == 1 ) { // colormapped (paletted) image - if (tga_image_type != 1 && tga_image_type != 9) { - stbi__rewind(s); - return 0; - } - stbi__skip(s,4); // skip index of first colormap entry and number of entries - sz = stbi__get8(s); // check bits per palette color entry - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { - stbi__rewind(s); - return 0; - } - stbi__skip(s,4); // skip image x and y origin - tga_colormap_bpp = sz; - } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE - if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { - stbi__rewind(s); - return 0; // only RGB or grey allowed, +/- RLE - } - stbi__skip(s,9); // skip colormap specification and image x/y origin - tga_colormap_bpp = 0; - } - tga_w = stbi__get16le(s); - if( tga_w < 1 ) { - stbi__rewind(s); - return 0; // test width - } - tga_h = stbi__get16le(s); - if( tga_h < 1 ) { - stbi__rewind(s); - return 0; // test height - } - tga_bits_per_pixel = stbi__get8(s); // bits per pixel - stbi__get8(s); // ignore alpha bits - if (tga_colormap_bpp != 0) { - if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { - // when using a colormap, tga_bits_per_pixel is the size of the indexes - // I don't think anything but 8 or 16bit indexes makes sense - stbi__rewind(s); - return 0; - } - tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); - } else { - tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); - } - if(!tga_comp) { - stbi__rewind(s); - return 0; - } - if (x) *x = tga_w; - if (y) *y = tga_h; - if (comp) *comp = tga_comp; - return 1; // seems to have passed everything -} - -static int stbi__tga_test(stbi__context *s) -{ - int res = 0; - int sz, tga_color_type; - stbi__get8(s); // discard Offset - tga_color_type = stbi__get8(s); // color type - if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed - sz = stbi__get8(s); // image type - if ( tga_color_type == 1 ) { // colormapped (paletted) image - if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 - stbi__skip(s,4); // skip index of first colormap entry and number of entries - sz = stbi__get8(s); // check bits per palette color entry - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; - stbi__skip(s,4); // skip image x and y origin - } else { // "normal" image w/o colormap - if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE - stbi__skip(s,9); // skip colormap specification and image x/y origin - } - if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width - if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height - sz = stbi__get8(s); // bits per pixel - if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; - - res = 1; // if we got this far, everything's good and we can return 1 instead of 0 - -errorEnd: - stbi__rewind(s); - return res; -} - -// read 16bit value and convert to 24bit RGB -static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) -{ - stbi__uint16 px = (stbi__uint16)stbi__get16le(s); - stbi__uint16 fiveBitMask = 31; - // we have 3 channels with 5bits each - int r = (px >> 10) & fiveBitMask; - int g = (px >> 5) & fiveBitMask; - int b = px & fiveBitMask; - // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later - out[0] = (stbi_uc)((r * 255)/31); - out[1] = (stbi_uc)((g * 255)/31); - out[2] = (stbi_uc)((b * 255)/31); - - // some people claim that the most significant bit might be used for alpha - // (possibly if an alpha-bit is set in the "image descriptor byte") - // but that only made 16bit test images completely translucent.. - // so let's treat all 15 and 16bit TGAs as RGB with no alpha. -} - -static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - // read in the TGA header stuff - int tga_offset = stbi__get8(s); - int tga_indexed = stbi__get8(s); - int tga_image_type = stbi__get8(s); - int tga_is_RLE = 0; - int tga_palette_start = stbi__get16le(s); - int tga_palette_len = stbi__get16le(s); - int tga_palette_bits = stbi__get8(s); - int tga_x_origin = stbi__get16le(s); - int tga_y_origin = stbi__get16le(s); - int tga_width = stbi__get16le(s); - int tga_height = stbi__get16le(s); - int tga_bits_per_pixel = stbi__get8(s); - int tga_comp, tga_rgb16=0; - int tga_inverted = stbi__get8(s); - // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) - // image data - unsigned char *tga_data; - unsigned char *tga_palette = NULL; - int i, j; - unsigned char raw_data[4] = {0}; - int RLE_count = 0; - int RLE_repeating = 0; - int read_next_pixel = 1; - STBI_NOTUSED(ri); - - // do a tiny bit of precessing - if ( tga_image_type >= 8 ) - { - tga_image_type -= 8; - tga_is_RLE = 1; - } - tga_inverted = 1 - ((tga_inverted >> 5) & 1); - - // If I'm paletted, then I'll use the number of bits from the palette - if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); - else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); - - if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency - return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); - - // tga info - *x = tga_width; - *y = tga_height; - if (comp) *comp = tga_comp; - - if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) - return stbi__errpuc("too large", "Corrupt TGA"); - - tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); - if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); - - // skip to the data's starting position (offset usually = 0) - stbi__skip(s, tga_offset ); - - if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { - for (i=0; i < tga_height; ++i) { - int row = tga_inverted ? tga_height -i - 1 : i; - stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; - stbi__getn(s, tga_row, tga_width * tga_comp); - } - } else { - // do I need to load a palette? - if ( tga_indexed) - { - // any data to skip? (offset usually = 0) - stbi__skip(s, tga_palette_start ); - // load the palette - tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); - if (!tga_palette) { - STBI_FREE(tga_data); - return stbi__errpuc("outofmem", "Out of memory"); - } - if (tga_rgb16) { - stbi_uc *pal_entry = tga_palette; - STBI_ASSERT(tga_comp == STBI_rgb); - for (i=0; i < tga_palette_len; ++i) { - stbi__tga_read_rgb16(s, pal_entry); - pal_entry += tga_comp; - } - } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { - STBI_FREE(tga_data); - STBI_FREE(tga_palette); - return stbi__errpuc("bad palette", "Corrupt TGA"); - } - } - // load the data - for (i=0; i < tga_width * tga_height; ++i) - { - // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? - if ( tga_is_RLE ) - { - if ( RLE_count == 0 ) - { - // yep, get the next byte as a RLE command - int RLE_cmd = stbi__get8(s); - RLE_count = 1 + (RLE_cmd & 127); - RLE_repeating = RLE_cmd >> 7; - read_next_pixel = 1; - } else if ( !RLE_repeating ) - { - read_next_pixel = 1; - } - } else - { - read_next_pixel = 1; - } - // OK, if I need to read a pixel, do it now - if ( read_next_pixel ) - { - // load however much data we did have - if ( tga_indexed ) - { - // read in index, then perform the lookup - int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); - if ( pal_idx >= tga_palette_len ) { - // invalid index - pal_idx = 0; - } - pal_idx *= tga_comp; - for (j = 0; j < tga_comp; ++j) { - raw_data[j] = tga_palette[pal_idx+j]; - } - } else if(tga_rgb16) { - STBI_ASSERT(tga_comp == STBI_rgb); - stbi__tga_read_rgb16(s, raw_data); - } else { - // read in the data raw - for (j = 0; j < tga_comp; ++j) { - raw_data[j] = stbi__get8(s); - } - } - // clear the reading flag for the next pixel - read_next_pixel = 0; - } // end of reading a pixel - - // copy data - for (j = 0; j < tga_comp; ++j) - tga_data[i*tga_comp+j] = raw_data[j]; - - // in case we're in RLE mode, keep counting down - --RLE_count; - } - // do I need to invert the image? - if ( tga_inverted ) - { - for (j = 0; j*2 < tga_height; ++j) - { - int index1 = j * tga_width * tga_comp; - int index2 = (tga_height - 1 - j) * tga_width * tga_comp; - for (i = tga_width * tga_comp; i > 0; --i) - { - unsigned char temp = tga_data[index1]; - tga_data[index1] = tga_data[index2]; - tga_data[index2] = temp; - ++index1; - ++index2; - } - } - } - // clear my palette, if I had one - if ( tga_palette != NULL ) - { - STBI_FREE( tga_palette ); - } - } - - // swap RGB - if the source data was RGB16, it already is in the right order - if (tga_comp >= 3 && !tga_rgb16) - { - unsigned char* tga_pixel = tga_data; - for (i=0; i < tga_width * tga_height; ++i) - { - unsigned char temp = tga_pixel[0]; - tga_pixel[0] = tga_pixel[2]; - tga_pixel[2] = temp; - tga_pixel += tga_comp; - } - } - - // convert to target component count - if (req_comp && req_comp != tga_comp) - tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); - - // the things I do to get rid of an error message, and yet keep - // Microsoft's C compilers happy... [8^( - tga_palette_start = tga_palette_len = tga_palette_bits = - tga_x_origin = tga_y_origin = 0; - // OK, done - return tga_data; -} -#endif - -// ************************************************************************************************* -// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB - -#ifndef STBI_NO_PSD -static int stbi__psd_test(stbi__context *s) -{ - int r = (stbi__get32be(s) == 0x38425053); - stbi__rewind(s); - return r; -} - -static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) -{ - int count, nleft, len; - - count = 0; - while ((nleft = pixelCount - count) > 0) { - len = stbi__get8(s); - if (len == 128) { - // No-op. - } else if (len < 128) { - // Copy next len+1 bytes literally. - len++; - if (len > nleft) return 0; // corrupt data - count += len; - while (len) { - *p = stbi__get8(s); - p += 4; - len--; - } - } else if (len > 128) { - stbi_uc val; - // Next -len+1 bytes in the dest are replicated from next source byte. - // (Interpret len as a negative 8-bit int.) - len = 257 - len; - if (len > nleft) return 0; // corrupt data - val = stbi__get8(s); - count += len; - while (len) { - *p = val; - p += 4; - len--; - } - } - } - - return 1; -} - -static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) -{ - int pixelCount; - int channelCount, compression; - int channel, i; - int bitdepth; - int w,h; - stbi_uc *out; - STBI_NOTUSED(ri); - - // Check identifier - if (stbi__get32be(s) != 0x38425053) // "8BPS" - return stbi__errpuc("not PSD", "Corrupt PSD image"); - - // Check file type version. - if (stbi__get16be(s) != 1) - return stbi__errpuc("wrong version", "Unsupported version of PSD image"); - - // Skip 6 reserved bytes. - stbi__skip(s, 6 ); - - // Read the number of channels (R, G, B, A, etc). - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) - return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); - - // Read the rows and columns of the image. - h = stbi__get32be(s); - w = stbi__get32be(s); - - // Make sure the depth is 8 bits. - bitdepth = stbi__get16be(s); - if (bitdepth != 8 && bitdepth != 16) - return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); - - // Make sure the color mode is RGB. - // Valid options are: - // 0: Bitmap - // 1: Grayscale - // 2: Indexed color - // 3: RGB color - // 4: CMYK color - // 7: Multichannel - // 8: Duotone - // 9: Lab color - if (stbi__get16be(s) != 3) - return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); - - // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) - stbi__skip(s,stbi__get32be(s) ); - - // Skip the image resources. (resolution, pen tool paths, etc) - stbi__skip(s, stbi__get32be(s) ); - - // Skip the reserved data. - stbi__skip(s, stbi__get32be(s) ); - - // Find out if the data is compressed. - // Known values: - // 0: no compression - // 1: RLE compressed - compression = stbi__get16be(s); - if (compression > 1) - return stbi__errpuc("bad compression", "PSD has an unknown compression format"); - - // Check size - if (!stbi__mad3sizes_valid(4, w, h, 0)) - return stbi__errpuc("too large", "Corrupt PSD"); - - // Create the destination image. - - if (!compression && bitdepth == 16 && bpc == 16) { - out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); - ri->bits_per_channel = 16; - } else - out = (stbi_uc *) stbi__malloc(4 * w*h); - - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - pixelCount = w*h; - - // Initialize the data to zero. - //memset( out, 0, pixelCount * 4 ); - - // Finally, the image data. - if (compression) { - // RLE as used by .PSD and .TIFF - // Loop until you get the number of unpacked bytes you are expecting: - // Read the next source byte into n. - // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. - // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. - // Else if n is 128, noop. - // Endloop - - // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, - // which we're going to just skip. - stbi__skip(s, h * channelCount * 2 ); - - // Read the RLE data by channel. - for (channel = 0; channel < 4; channel++) { - stbi_uc *p; - - p = out+channel; - if (channel >= channelCount) { - // Fill this channel with default data. - for (i = 0; i < pixelCount; i++, p += 4) - *p = (channel == 3 ? 255 : 0); - } else { - // Read the RLE data. - if (!stbi__psd_decode_rle(s, p, pixelCount)) { - STBI_FREE(out); - return stbi__errpuc("corrupt", "bad RLE data"); - } - } - } - - } else { - // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) - // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. - - // Read the data by channel. - for (channel = 0; channel < 4; channel++) { - if (channel >= channelCount) { - // Fill this channel with default data. - if (bitdepth == 16 && bpc == 16) { - stbi__uint16 *q = ((stbi__uint16 *) out) + channel; - stbi__uint16 val = channel == 3 ? 65535 : 0; - for (i = 0; i < pixelCount; i++, q += 4) - *q = val; - } else { - stbi_uc *p = out+channel; - stbi_uc val = channel == 3 ? 255 : 0; - for (i = 0; i < pixelCount; i++, p += 4) - *p = val; - } - } else { - if (ri->bits_per_channel == 16) { // output bpc - stbi__uint16 *q = ((stbi__uint16 *) out) + channel; - for (i = 0; i < pixelCount; i++, q += 4) - *q = (stbi__uint16) stbi__get16be(s); - } else { - stbi_uc *p = out+channel; - if (bitdepth == 16) { // input bpc - for (i = 0; i < pixelCount; i++, p += 4) - *p = (stbi_uc) (stbi__get16be(s) >> 8); - } else { - for (i = 0; i < pixelCount; i++, p += 4) - *p = stbi__get8(s); - } - } - } - } - } - - // remove weird white matte from PSD - if (channelCount >= 4) { - if (ri->bits_per_channel == 16) { - for (i=0; i < w*h; ++i) { - stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; - if (pixel[3] != 0 && pixel[3] != 65535) { - float a = pixel[3] / 65535.0f; - float ra = 1.0f / a; - float inv_a = 65535.0f * (1 - ra); - pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); - pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); - pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); - } - } - } else { - for (i=0; i < w*h; ++i) { - unsigned char *pixel = out + 4*i; - if (pixel[3] != 0 && pixel[3] != 255) { - float a = pixel[3] / 255.0f; - float ra = 1.0f / a; - float inv_a = 255.0f * (1 - ra); - pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); - pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); - pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); - } - } - } - } - - // convert to desired output format - if (req_comp && req_comp != 4) { - if (ri->bits_per_channel == 16) - out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); - else - out = stbi__convert_format(out, 4, req_comp, w, h); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - - if (comp) *comp = 4; - *y = h; - *x = w; - - return out; -} -#endif - -// ************************************************************************************************* -// Softimage PIC loader -// by Tom Seddon -// -// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format -// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ - -#ifndef STBI_NO_PIC -static int stbi__pic_is4(stbi__context *s,const char *str) -{ - int i; - for (i=0; i<4; ++i) - if (stbi__get8(s) != (stbi_uc)str[i]) - return 0; - - return 1; -} - -static int stbi__pic_test_core(stbi__context *s) -{ - int i; - - if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) - return 0; - - for(i=0;i<84;++i) - stbi__get8(s); - - if (!stbi__pic_is4(s,"PICT")) - return 0; - - return 1; -} - -typedef struct -{ - stbi_uc size,type,channel; -} stbi__pic_packet; - -static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) -{ - int mask=0x80, i; - - for (i=0; i<4; ++i, mask>>=1) { - if (channel & mask) { - if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); - dest[i]=stbi__get8(s); - } - } - - return dest; -} - -static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) -{ - int mask=0x80,i; - - for (i=0;i<4; ++i, mask>>=1) - if (channel&mask) - dest[i]=src[i]; -} - -static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) -{ - int act_comp=0,num_packets=0,y,chained; - stbi__pic_packet packets[10]; - - // this will (should...) cater for even some bizarre stuff like having data - // for the same channel in multiple packets. - do { - stbi__pic_packet *packet; - - if (num_packets==sizeof(packets)/sizeof(packets[0])) - return stbi__errpuc("bad format","too many packets"); - - packet = &packets[num_packets++]; - - chained = stbi__get8(s); - packet->size = stbi__get8(s); - packet->type = stbi__get8(s); - packet->channel = stbi__get8(s); - - act_comp |= packet->channel; - - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); - if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); - } while (chained); - - *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? - - for(y=0; ytype) { - default: - return stbi__errpuc("bad format","packet has bad compression type"); - - case 0: {//uncompressed - int x; - - for(x=0;xchannel,dest)) - return 0; - break; - } - - case 1://Pure RLE - { - int left=width, i; - - while (left>0) { - stbi_uc count,value[4]; - - count=stbi__get8(s); - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); - - if (count > left) - count = (stbi_uc) left; - - if (!stbi__readval(s,packet->channel,value)) return 0; - - for(i=0; ichannel,dest,value); - left -= count; - } - } - break; - - case 2: {//Mixed RLE - int left=width; - while (left>0) { - int count = stbi__get8(s), i; - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); - - if (count >= 128) { // Repeated - stbi_uc value[4]; - - if (count==128) - count = stbi__get16be(s); - else - count -= 127; - if (count > left) - return stbi__errpuc("bad file","scanline overrun"); - - if (!stbi__readval(s,packet->channel,value)) - return 0; - - for(i=0;ichannel,dest,value); - } else { // Raw - ++count; - if (count>left) return stbi__errpuc("bad file","scanline overrun"); - - for(i=0;ichannel,dest)) - return 0; - } - left-=count; - } - break; - } - } - } - } - - return result; -} - -static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) -{ - stbi_uc *result; - int i, x,y, internal_comp; - STBI_NOTUSED(ri); - - if (!comp) comp = &internal_comp; - - for (i=0; i<92; ++i) - stbi__get8(s); - - x = stbi__get16be(s); - y = stbi__get16be(s); - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); - if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); - - stbi__get32be(s); //skip `ratio' - stbi__get16be(s); //skip `fields' - stbi__get16be(s); //skip `pad' - - // intermediate buffer is RGBA - result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); - memset(result, 0xff, x*y*4); - - if (!stbi__pic_load_core(s,x,y,comp, result)) { - STBI_FREE(result); - result=0; - } - *px = x; - *py = y; - if (req_comp == 0) req_comp = *comp; - result=stbi__convert_format(result,4,req_comp,x,y); - - return result; -} - -static int stbi__pic_test(stbi__context *s) -{ - int r = stbi__pic_test_core(s); - stbi__rewind(s); - return r; -} -#endif - -// ************************************************************************************************* -// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb - -#ifndef STBI_NO_GIF -typedef struct -{ - stbi__int16 prefix; - stbi_uc first; - stbi_uc suffix; -} stbi__gif_lzw; - -typedef struct -{ - int w,h; - stbi_uc *out; // output buffer (always 4 components) - stbi_uc *background; // The current "background" as far as a gif is concerned - stbi_uc *history; - int flags, bgindex, ratio, transparent, eflags; - stbi_uc pal[256][4]; - stbi_uc lpal[256][4]; - stbi__gif_lzw codes[8192]; - stbi_uc *color_table; - int parse, step; - int lflags; - int start_x, start_y; - int max_x, max_y; - int cur_x, cur_y; - int line_size; - int delay; -} stbi__gif; - -static int stbi__gif_test_raw(stbi__context *s) -{ - int sz; - if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; - sz = stbi__get8(s); - if (sz != '9' && sz != '7') return 0; - if (stbi__get8(s) != 'a') return 0; - return 1; -} - -static int stbi__gif_test(stbi__context *s) -{ - int r = stbi__gif_test_raw(s); - stbi__rewind(s); - return r; -} - -static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) -{ - int i; - for (i=0; i < num_entries; ++i) { - pal[i][2] = stbi__get8(s); - pal[i][1] = stbi__get8(s); - pal[i][0] = stbi__get8(s); - pal[i][3] = transp == i ? 0 : 255; - } -} - -static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) -{ - stbi_uc version; - if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') - return stbi__err("not GIF", "Corrupt GIF"); - - version = stbi__get8(s); - if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); - if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); - - stbi__g_failure_reason = ""; - g->w = stbi__get16le(s); - g->h = stbi__get16le(s); - g->flags = stbi__get8(s); - g->bgindex = stbi__get8(s); - g->ratio = stbi__get8(s); - g->transparent = -1; - - if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments - - if (is_info) return 1; - - if (g->flags & 0x80) - stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); - - return 1; -} - -static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) -{ - stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); - if (!stbi__gif_header(s, g, comp, 1)) { - STBI_FREE(g); - stbi__rewind( s ); - return 0; - } - if (x) *x = g->w; - if (y) *y = g->h; - STBI_FREE(g); - return 1; -} - -static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) -{ - stbi_uc *p, *c; - int idx; - - // recurse to decode the prefixes, since the linked-list is backwards, - // and working backwards through an interleaved image would be nasty - if (g->codes[code].prefix >= 0) - stbi__out_gif_code(g, g->codes[code].prefix); - - if (g->cur_y >= g->max_y) return; - - idx = g->cur_x + g->cur_y; - p = &g->out[idx]; - g->history[idx / 4] = 1; - - c = &g->color_table[g->codes[code].suffix * 4]; - if (c[3] > 128) { // don't render transparent pixels; - p[0] = c[2]; - p[1] = c[1]; - p[2] = c[0]; - p[3] = c[3]; - } - g->cur_x += 4; - - if (g->cur_x >= g->max_x) { - g->cur_x = g->start_x; - g->cur_y += g->step; - - while (g->cur_y >= g->max_y && g->parse > 0) { - g->step = (1 << g->parse) * g->line_size; - g->cur_y = g->start_y + (g->step >> 1); - --g->parse; - } - } -} - -static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) -{ - stbi_uc lzw_cs; - stbi__int32 len, init_code; - stbi__uint32 first; - stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; - stbi__gif_lzw *p; - - lzw_cs = stbi__get8(s); - if (lzw_cs > 12) return NULL; - clear = 1 << lzw_cs; - first = 1; - codesize = lzw_cs + 1; - codemask = (1 << codesize) - 1; - bits = 0; - valid_bits = 0; - for (init_code = 0; init_code < clear; init_code++) { - g->codes[init_code].prefix = -1; - g->codes[init_code].first = (stbi_uc) init_code; - g->codes[init_code].suffix = (stbi_uc) init_code; - } - - // support no starting clear code - avail = clear+2; - oldcode = -1; - - len = 0; - for(;;) { - if (valid_bits < codesize) { - if (len == 0) { - len = stbi__get8(s); // start new block - if (len == 0) - return g->out; - } - --len; - bits |= (stbi__int32) stbi__get8(s) << valid_bits; - valid_bits += 8; - } else { - stbi__int32 code = bits & codemask; - bits >>= codesize; - valid_bits -= codesize; - // @OPTIMIZE: is there some way we can accelerate the non-clear path? - if (code == clear) { // clear code - codesize = lzw_cs + 1; - codemask = (1 << codesize) - 1; - avail = clear + 2; - oldcode = -1; - first = 0; - } else if (code == clear + 1) { // end of stream code - stbi__skip(s, len); - while ((len = stbi__get8(s)) > 0) - stbi__skip(s,len); - return g->out; - } else if (code <= avail) { - if (first) { - return stbi__errpuc("no clear code", "Corrupt GIF"); - } - - if (oldcode >= 0) { - p = &g->codes[avail++]; - if (avail > 8192) { - return stbi__errpuc("too many codes", "Corrupt GIF"); - } - - p->prefix = (stbi__int16) oldcode; - p->first = g->codes[oldcode].first; - p->suffix = (code == avail) ? p->first : g->codes[code].first; - } else if (code == avail) - return stbi__errpuc("illegal code in raster", "Corrupt GIF"); - - stbi__out_gif_code(g, (stbi__uint16) code); - - if ((avail & codemask) == 0 && avail <= 0x0FFF) { - codesize++; - codemask = (1 << codesize) - 1; - } - - oldcode = code; - } else { - return stbi__errpuc("illegal code in raster", "Corrupt GIF"); - } - } - } -} - -// this function is designed to support animated gifs, although stb_image doesn't support it -// two back is the image from two frames ago, used for a very specific disposal format -static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) -{ - int dispose; - int first_frame; - int pi; - int pcount; - - // on first frame, any non-written pixels get the background colour (non-transparent) - first_frame = 0; - if (g->out == 0) { - if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header - g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); - g->background = (stbi_uc *) stbi__malloc(4 * g->w * g->h); - g->history = (stbi_uc *) stbi__malloc(g->w * g->h); - if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); - - // image is treated as "tranparent" at the start - ie, nothing overwrites the current background; - // background colour is only used for pixels that are not rendered first frame, after that "background" - // color refers to teh color that was there the previous frame. - memset( g->out, 0x00, 4 * g->w * g->h ); - memset( g->background, 0x00, 4 * g->w * g->h ); // state of the background (starts transparent) - memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame - first_frame = 1; - } else { - // second frame - how do we dispoase of the previous one? - dispose = (g->eflags & 0x1C) >> 2; - pcount = g->w * g->h; - - if ((dispose == 3) && (two_back == 0)) { - dispose = 2; // if I don't have an image to revert back to, default to the old background - } - - if (dispose == 3) { // use previous graphic - for (pi = 0; pi < pcount; ++pi) { - if (g->history[pi]) { - memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); - } - } - } else if (dispose == 2) { - // restore what was changed last frame to background before that frame; - for (pi = 0; pi < pcount; ++pi) { - if (g->history[pi]) { - memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); - } - } - } else { - // This is a non-disposal case eithe way, so just - // leave the pixels as is, and they will become the new background - // 1: do not dispose - // 0: not specified. - } - - // background is what out is after the undoing of the previou frame; - memcpy( g->background, g->out, 4 * g->w * g->h ); - } - - // clear my history; - memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame - - for (;;) { - int tag = stbi__get8(s); - switch (tag) { - case 0x2C: /* Image Descriptor */ - { - stbi__int32 x, y, w, h; - stbi_uc *o; - - x = stbi__get16le(s); - y = stbi__get16le(s); - w = stbi__get16le(s); - h = stbi__get16le(s); - if (((x + w) > (g->w)) || ((y + h) > (g->h))) - return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); - - g->line_size = g->w * 4; - g->start_x = x * 4; - g->start_y = y * g->line_size; - g->max_x = g->start_x + w * 4; - g->max_y = g->start_y + h * g->line_size; - g->cur_x = g->start_x; - g->cur_y = g->start_y; - - g->lflags = stbi__get8(s); - - if (g->lflags & 0x40) { - g->step = 8 * g->line_size; // first interlaced spacing - g->parse = 3; - } else { - g->step = g->line_size; - g->parse = 0; - } - - if (g->lflags & 0x80) { - stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); - g->color_table = (stbi_uc *) g->lpal; - } else if (g->flags & 0x80) { - g->color_table = (stbi_uc *) g->pal; - } else - return stbi__errpuc("missing color table", "Corrupt GIF"); - - o = stbi__process_gif_raster(s, g); - if (o == NULL) return NULL; - - // if this was the first frame, - pcount = g->w * g->h; - if (first_frame && (g->bgindex > 0)) { - // if first frame, any pixel not drawn to gets the background color - for (pi = 0; pi < pcount; ++pi) { - if (g->history[pi] == 0) { - g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; - memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); - } - } - } - - return o; - } - - case 0x21: // Comment Extension. - { - int len; - int ext = stbi__get8(s); - if (ext == 0xF9) { // Graphic Control Extension. - len = stbi__get8(s); - if (len == 4) { - g->eflags = stbi__get8(s); - g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. - - // unset old transparent - if (g->transparent >= 0) { - g->pal[g->transparent][3] = 255; - } - if (g->eflags & 0x01) { - g->transparent = stbi__get8(s); - if (g->transparent >= 0) { - g->pal[g->transparent][3] = 0; - } - } else { - // don't need transparent - stbi__skip(s, 1); - g->transparent = -1; - } - } else { - stbi__skip(s, len); - break; - } - } - while ((len = stbi__get8(s)) != 0) { - stbi__skip(s, len); - } - break; - } - - case 0x3B: // gif stream termination code - return (stbi_uc *) s; // using '1' causes warning on some compilers - - default: - return stbi__errpuc("unknown code", "Corrupt GIF"); - } - } -} - -static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) -{ - if (stbi__gif_test(s)) { - int layers = 0; - stbi_uc *u = 0; - stbi_uc *out = 0; - stbi_uc *two_back = 0; - stbi__gif g; - int stride; - memset(&g, 0, sizeof(g)); - if (delays) { - *delays = 0; - } - - do { - u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); - if (u == (stbi_uc *) s) u = 0; // end of animated gif marker - - if (u) { - *x = g.w; - *y = g.h; - ++layers; - stride = g.w * g.h * 4; - - if (out) { - out = (stbi_uc*) STBI_REALLOC( out, layers * stride ); - if (delays) { - *delays = (int*) STBI_REALLOC( *delays, sizeof(int) * layers ); - } - } else { - out = (stbi_uc*)stbi__malloc( layers * stride ); - if (delays) { - *delays = (int*) stbi__malloc( layers * sizeof(int) ); - } - } - memcpy( out + ((layers - 1) * stride), u, stride ); - if (layers >= 2) { - two_back = out - 2 * stride; - } - - if (delays) { - (*delays)[layers - 1U] = g.delay; - } - } - } while (u != 0); - - // free temp buffer; - STBI_FREE(g.out); - STBI_FREE(g.history); - STBI_FREE(g.background); - - // do the final conversion after loading everything; - if (req_comp && req_comp != 4) - out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); - - *z = layers; - return out; - } else { - return stbi__errpuc("not GIF", "Image was not as a gif type."); - } -} - -static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi_uc *u = 0; - stbi__gif g; - memset(&g, 0, sizeof(g)); - - u = stbi__gif_load_next(s, &g, comp, req_comp, 0); - if (u == (stbi_uc *) s) u = 0; // end of animated gif marker - if (u) { - *x = g.w; - *y = g.h; - - // moved conversion to after successful load so that the same - // can be done for multiple frames. - if (req_comp && req_comp != 4) - u = stbi__convert_format(u, 4, req_comp, g.w, g.h); - } - - // free buffers needed for multiple frame loading; - STBI_FREE(g.history); - STBI_FREE(g.background); - - return u; -} - -static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) -{ - return stbi__gif_info_raw(s,x,y,comp); -} -#endif - -// ************************************************************************************************* -// Radiance RGBE HDR loader -// originally by Nicolas Schulz -#ifndef STBI_NO_HDR -static int stbi__hdr_test_core(stbi__context *s, const char *signature) -{ - int i; - for (i=0; signature[i]; ++i) - if (stbi__get8(s) != signature[i]) - return 0; - stbi__rewind(s); - return 1; -} - -static int stbi__hdr_test(stbi__context* s) -{ - int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); - stbi__rewind(s); - if(!r) { - r = stbi__hdr_test_core(s, "#?RGBE\n"); - stbi__rewind(s); - } - return r; -} - -#define STBI__HDR_BUFLEN 1024 -static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) -{ - int len=0; - char c = '\0'; - - c = (char) stbi__get8(z); - - while (!stbi__at_eof(z) && c != '\n') { - buffer[len++] = c; - if (len == STBI__HDR_BUFLEN-1) { - // flush to end of line - while (!stbi__at_eof(z) && stbi__get8(z) != '\n') - ; - break; - } - c = (char) stbi__get8(z); - } - - buffer[len] = 0; - return buffer; -} - -static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) -{ - if ( input[3] != 0 ) { - float f1; - // Exponent - f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); - if (req_comp <= 2) - output[0] = (input[0] + input[1] + input[2]) * f1 / 3; - else { - output[0] = input[0] * f1; - output[1] = input[1] * f1; - output[2] = input[2] * f1; - } - if (req_comp == 2) output[1] = 1; - if (req_comp == 4) output[3] = 1; - } else { - switch (req_comp) { - case 4: output[3] = 1; /* fallthrough */ - case 3: output[0] = output[1] = output[2] = 0; - break; - case 2: output[1] = 1; /* fallthrough */ - case 1: output[0] = 0; - break; - } - } -} - -static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - char buffer[STBI__HDR_BUFLEN]; - char *token; - int valid = 0; - int width, height; - stbi_uc *scanline; - float *hdr_data; - int len; - unsigned char count, value; - int i, j, k, c1,c2, z; - const char *headerToken; - STBI_NOTUSED(ri); - - // Check identifier - headerToken = stbi__hdr_gettoken(s,buffer); - if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) - return stbi__errpf("not HDR", "Corrupt HDR image"); - - // Parse header - for(;;) { - token = stbi__hdr_gettoken(s,buffer); - if (token[0] == 0) break; - if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; - } - - if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); - - // Parse width and height - // can't use sscanf() if we're not using stdio! - token = stbi__hdr_gettoken(s,buffer); - if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); - token += 3; - height = (int) strtol(token, &token, 10); - while (*token == ' ') ++token; - if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); - token += 3; - width = (int) strtol(token, NULL, 10); - - *x = width; - *y = height; - - if (comp) *comp = 3; - if (req_comp == 0) req_comp = 3; - - if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) - return stbi__errpf("too large", "HDR image is too large"); - - // Read data - hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); - if (!hdr_data) - return stbi__errpf("outofmem", "Out of memory"); - - // Load image data - // image data is stored as some number of sca - if ( width < 8 || width >= 32768) { - // Read flat data - for (j=0; j < height; ++j) { - for (i=0; i < width; ++i) { - stbi_uc rgbe[4]; - main_decode_loop: - stbi__getn(s, rgbe, 4); - stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); - } - } - } else { - // Read RLE-encoded data - scanline = NULL; - - for (j = 0; j < height; ++j) { - c1 = stbi__get8(s); - c2 = stbi__get8(s); - len = stbi__get8(s); - if (c1 != 2 || c2 != 2 || (len & 0x80)) { - // not run-length encoded, so we have to actually use THIS data as a decoded - // pixel (note this can't be a valid pixel--one of RGB must be >= 128) - stbi_uc rgbe[4]; - rgbe[0] = (stbi_uc) c1; - rgbe[1] = (stbi_uc) c2; - rgbe[2] = (stbi_uc) len; - rgbe[3] = (stbi_uc) stbi__get8(s); - stbi__hdr_convert(hdr_data, rgbe, req_comp); - i = 1; - j = 0; - STBI_FREE(scanline); - goto main_decode_loop; // yes, this makes no sense - } - len <<= 8; - len |= stbi__get8(s); - if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } - if (scanline == NULL) { - scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); - if (!scanline) { - STBI_FREE(hdr_data); - return stbi__errpf("outofmem", "Out of memory"); - } - } - - for (k = 0; k < 4; ++k) { - int nleft; - i = 0; - while ((nleft = width - i) > 0) { - count = stbi__get8(s); - if (count > 128) { - // Run - value = stbi__get8(s); - count -= 128; - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = value; - } else { - // Dump - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = stbi__get8(s); - } - } - } - for (i=0; i < width; ++i) - stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); - } - if (scanline) - STBI_FREE(scanline); - } - - return hdr_data; -} - -static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) -{ - char buffer[STBI__HDR_BUFLEN]; - char *token; - int valid = 0; - int dummy; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - if (stbi__hdr_test(s) == 0) { - stbi__rewind( s ); - return 0; - } - - for(;;) { - token = stbi__hdr_gettoken(s,buffer); - if (token[0] == 0) break; - if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; - } - - if (!valid) { - stbi__rewind( s ); - return 0; - } - token = stbi__hdr_gettoken(s,buffer); - if (strncmp(token, "-Y ", 3)) { - stbi__rewind( s ); - return 0; - } - token += 3; - *y = (int) strtol(token, &token, 10); - while (*token == ' ') ++token; - if (strncmp(token, "+X ", 3)) { - stbi__rewind( s ); - return 0; - } - token += 3; - *x = (int) strtol(token, NULL, 10); - *comp = 3; - return 1; -} -#endif // STBI_NO_HDR - -#ifndef STBI_NO_BMP -static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) -{ - void *p; - stbi__bmp_data info; - - info.all_a = 255; - p = stbi__bmp_parse_header(s, &info); - stbi__rewind( s ); - if (p == NULL) - return 0; - if (x) *x = s->img_x; - if (y) *y = s->img_y; - if (comp) *comp = info.ma ? 4 : 3; - return 1; -} -#endif - -#ifndef STBI_NO_PSD -static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) -{ - int channelCount, dummy, depth; - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - if (stbi__get32be(s) != 0x38425053) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 1) { - stbi__rewind( s ); - return 0; - } - stbi__skip(s, 6); - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) { - stbi__rewind( s ); - return 0; - } - *y = stbi__get32be(s); - *x = stbi__get32be(s); - depth = stbi__get16be(s); - if (depth != 8 && depth != 16) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 3) { - stbi__rewind( s ); - return 0; - } - *comp = 4; - return 1; -} - -static int stbi__psd_is16(stbi__context *s) -{ - int channelCount, depth; - if (stbi__get32be(s) != 0x38425053) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 1) { - stbi__rewind( s ); - return 0; - } - stbi__skip(s, 6); - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) { - stbi__rewind( s ); - return 0; - } - (void) stbi__get32be(s); - (void) stbi__get32be(s); - depth = stbi__get16be(s); - if (depth != 16) { - stbi__rewind( s ); - return 0; - } - return 1; -} -#endif - -#ifndef STBI_NO_PIC -static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) -{ - int act_comp=0,num_packets=0,chained,dummy; - stbi__pic_packet packets[10]; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { - stbi__rewind(s); - return 0; - } - - stbi__skip(s, 88); - - *x = stbi__get16be(s); - *y = stbi__get16be(s); - if (stbi__at_eof(s)) { - stbi__rewind( s); - return 0; - } - if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { - stbi__rewind( s ); - return 0; - } - - stbi__skip(s, 8); - - do { - stbi__pic_packet *packet; - - if (num_packets==sizeof(packets)/sizeof(packets[0])) - return 0; - - packet = &packets[num_packets++]; - chained = stbi__get8(s); - packet->size = stbi__get8(s); - packet->type = stbi__get8(s); - packet->channel = stbi__get8(s); - act_comp |= packet->channel; - - if (stbi__at_eof(s)) { - stbi__rewind( s ); - return 0; - } - if (packet->size != 8) { - stbi__rewind( s ); - return 0; - } - } while (chained); - - *comp = (act_comp & 0x10 ? 4 : 3); - - return 1; -} -#endif - -// ************************************************************************************************* -// Portable Gray Map and Portable Pixel Map loader -// by Ken Miller -// -// PGM: http://netpbm.sourceforge.net/doc/pgm.html -// PPM: http://netpbm.sourceforge.net/doc/ppm.html -// -// Known limitations: -// Does not support comments in the header section -// Does not support ASCII image data (formats P2 and P3) -// Does not support 16-bit-per-channel - -#ifndef STBI_NO_PNM - -static int stbi__pnm_test(stbi__context *s) -{ - char p, t; - p = (char) stbi__get8(s); - t = (char) stbi__get8(s); - if (p != 'P' || (t != '5' && t != '6')) { - stbi__rewind( s ); - return 0; - } - return 1; -} - -static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi_uc *out; - STBI_NOTUSED(ri); - - if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) - return 0; - - *x = s->img_x; - *y = s->img_y; - if (comp) *comp = s->img_n; - - if (!stbi__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0)) - return stbi__errpuc("too large", "PNM too large"); - - out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0); - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - stbi__getn(s, out, s->img_n * s->img_x * s->img_y); - - if (req_comp && req_comp != s->img_n) { - out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - return out; -} - -static int stbi__pnm_isspace(char c) -{ - return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; -} - -static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) -{ - for (;;) { - while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) - *c = (char) stbi__get8(s); - - if (stbi__at_eof(s) || *c != '#') - break; - - while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) - *c = (char) stbi__get8(s); - } -} - -static int stbi__pnm_isdigit(char c) -{ - return c >= '0' && c <= '9'; -} - -static int stbi__pnm_getinteger(stbi__context *s, char *c) -{ - int value = 0; - - while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { - value = value*10 + (*c - '0'); - *c = (char) stbi__get8(s); - } - - return value; -} - -static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) -{ - int maxv, dummy; - char c, p, t; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - stbi__rewind(s); - - // Get identifier - p = (char) stbi__get8(s); - t = (char) stbi__get8(s); - if (p != 'P' || (t != '5' && t != '6')) { - stbi__rewind(s); - return 0; - } - - *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm - - c = (char) stbi__get8(s); - stbi__pnm_skip_whitespace(s, &c); - - *x = stbi__pnm_getinteger(s, &c); // read width - stbi__pnm_skip_whitespace(s, &c); - - *y = stbi__pnm_getinteger(s, &c); // read height - stbi__pnm_skip_whitespace(s, &c); - - maxv = stbi__pnm_getinteger(s, &c); // read max value - - if (maxv > 255) - return stbi__err("max value > 255", "PPM image not 8-bit"); - else - return 1; -} -#endif - -static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) -{ - #ifndef STBI_NO_JPEG - if (stbi__jpeg_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PNG - if (stbi__png_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_GIF - if (stbi__gif_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_BMP - if (stbi__bmp_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PSD - if (stbi__psd_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PIC - if (stbi__pic_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PNM - if (stbi__pnm_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_HDR - if (stbi__hdr_info(s, x, y, comp)) return 1; - #endif - - // test tga last because it's a crappy test! - #ifndef STBI_NO_TGA - if (stbi__tga_info(s, x, y, comp)) - return 1; - #endif - return stbi__err("unknown image type", "Image not of any known type, or corrupt"); -} - -static int stbi__is_16_main(stbi__context *s) -{ - #ifndef STBI_NO_PNG - if (stbi__png_is16(s)) return 1; - #endif - - #ifndef STBI_NO_PSD - if (stbi__psd_is16(s)) return 1; - #endif - - return 0; -} - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result; - if (!f) return stbi__err("can't fopen", "Unable to open file"); - result = stbi_info_from_file(f, x, y, comp); - fclose(f); - return result; -} - -STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) -{ - int r; - stbi__context s; - long pos = ftell(f); - stbi__start_file(&s, f); - r = stbi__info_main(&s,x,y,comp); - fseek(f,pos,SEEK_SET); - return r; -} - -STBIDEF int stbi_is_16_bit(char const *filename) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result; - if (!f) return stbi__err("can't fopen", "Unable to open file"); - result = stbi_is_16_bit_from_file(f); - fclose(f); - return result; -} - -STBIDEF int stbi_is_16_bit_from_file(FILE *f) -{ - int r; - stbi__context s; - long pos = ftell(f); - stbi__start_file(&s, f); - r = stbi__is_16_main(&s); - fseek(f,pos,SEEK_SET); - return r; -} -#endif // !STBI_NO_STDIO - -STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__info_main(&s,x,y,comp); -} - -STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); - return stbi__info_main(&s,x,y,comp); -} - -STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__is_16_main(&s); -} - -STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); - return stbi__is_16_main(&s); -} - -#endif // STB_IMAGE_IMPLEMENTATION - -/* - revision history: - 2.19 (2018-02-11) fix warning - 2.18 (2018-01-30) fix warnings - 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug - 1-bit BMP - *_is_16_bit api - avoid warnings - 2.16 (2017-07-23) all functions have 16-bit variants; - STBI_NO_STDIO works again; - compilation fixes; - fix rounding in unpremultiply; - optimize vertical flip; - disable raw_len validation; - documentation fixes - 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; - warning fixes; disable run-time SSE detection on gcc; - uniform handling of optional "return" values; - thread-safe initialization of zlib tables - 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs - 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now - 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes - 2.11 (2016-04-02) allocate large structures on the stack - remove white matting for transparent PSD - fix reported channel count for PNG & BMP - re-enable SSE2 in non-gcc 64-bit - support RGB-formatted JPEG - read 16-bit PNGs (only as 8-bit) - 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED - 2.09 (2016-01-16) allow comments in PNM files - 16-bit-per-pixel TGA (not bit-per-component) - info() for TGA could break due to .hdr handling - info() for BMP to shares code instead of sloppy parse - can use STBI_REALLOC_SIZED if allocator doesn't support realloc - code cleanup - 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA - 2.07 (2015-09-13) fix compiler warnings - partial animated GIF support - limited 16-bpc PSD support - #ifdef unused functions - bug with < 92 byte PIC,PNM,HDR,TGA - 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value - 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning - 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit - 2.03 (2015-04-12) extra corruption checking (mmozeiko) - stbi_set_flip_vertically_on_load (nguillemot) - fix NEON support; fix mingw support - 2.02 (2015-01-19) fix incorrect assert, fix warning - 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 - 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG - 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) - progressive JPEG (stb) - PGM/PPM support (Ken Miller) - STBI_MALLOC,STBI_REALLOC,STBI_FREE - GIF bugfix -- seemingly never worked - STBI_NO_*, STBI_ONLY_* - 1.48 (2014-12-14) fix incorrectly-named assert() - 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) - optimize PNG (ryg) - fix bug in interlaced PNG with user-specified channel count (stb) - 1.46 (2014-08-26) - fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG - 1.45 (2014-08-16) - fix MSVC-ARM internal compiler error by wrapping malloc - 1.44 (2014-08-07) - various warning fixes from Ronny Chevalier - 1.43 (2014-07-15) - fix MSVC-only compiler problem in code changed in 1.42 - 1.42 (2014-07-09) - don't define _CRT_SECURE_NO_WARNINGS (affects user code) - fixes to stbi__cleanup_jpeg path - added STBI_ASSERT to avoid requiring assert.h - 1.41 (2014-06-25) - fix search&replace from 1.36 that messed up comments/error messages - 1.40 (2014-06-22) - fix gcc struct-initialization warning - 1.39 (2014-06-15) - fix to TGA optimization when req_comp != number of components in TGA; - fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) - add support for BMP version 5 (more ignored fields) - 1.38 (2014-06-06) - suppress MSVC warnings on integer casts truncating values - fix accidental rename of 'skip' field of I/O - 1.37 (2014-06-04) - remove duplicate typedef - 1.36 (2014-06-03) - convert to header file single-file library - if de-iphone isn't set, load iphone images color-swapped instead of returning NULL - 1.35 (2014-05-27) - various warnings - fix broken STBI_SIMD path - fix bug where stbi_load_from_file no longer left file pointer in correct place - fix broken non-easy path for 32-bit BMP (possibly never used) - TGA optimization by Arseny Kapoulkine - 1.34 (unknown) - use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case - 1.33 (2011-07-14) - make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements - 1.32 (2011-07-13) - support for "info" function for all supported filetypes (SpartanJ) - 1.31 (2011-06-20) - a few more leak fixes, bug in PNG handling (SpartanJ) - 1.30 (2011-06-11) - added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) - removed deprecated format-specific test/load functions - removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway - error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) - fix inefficiency in decoding 32-bit BMP (David Woo) - 1.29 (2010-08-16) - various warning fixes from Aurelien Pocheville - 1.28 (2010-08-01) - fix bug in GIF palette transparency (SpartanJ) - 1.27 (2010-08-01) - cast-to-stbi_uc to fix warnings - 1.26 (2010-07-24) - fix bug in file buffering for PNG reported by SpartanJ - 1.25 (2010-07-17) - refix trans_data warning (Won Chun) - 1.24 (2010-07-12) - perf improvements reading from files on platforms with lock-heavy fgetc() - minor perf improvements for jpeg - deprecated type-specific functions so we'll get feedback if they're needed - attempt to fix trans_data warning (Won Chun) - 1.23 fixed bug in iPhone support - 1.22 (2010-07-10) - removed image *writing* support - stbi_info support from Jetro Lauha - GIF support from Jean-Marc Lienher - iPhone PNG-extensions from James Brown - warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) - 1.21 fix use of 'stbi_uc' in header (reported by jon blow) - 1.20 added support for Softimage PIC, by Tom Seddon - 1.19 bug in interlaced PNG corruption check (found by ryg) - 1.18 (2008-08-02) - fix a threading bug (local mutable static) - 1.17 support interlaced PNG - 1.16 major bugfix - stbi__convert_format converted one too many pixels - 1.15 initialize some fields for thread safety - 1.14 fix threadsafe conversion bug - header-file-only version (#define STBI_HEADER_FILE_ONLY before including) - 1.13 threadsafe - 1.12 const qualifiers in the API - 1.11 Support installable IDCT, colorspace conversion routines - 1.10 Fixes for 64-bit (don't use "unsigned long") - optimized upsampling by Fabian "ryg" Giesen - 1.09 Fix format-conversion for PSD code (bad global variables!) - 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz - 1.07 attempt to fix C++ warning/errors again - 1.06 attempt to fix C++ warning/errors again - 1.05 fix TGA loading to return correct *comp and use good luminance calc - 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free - 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR - 1.02 support for (subset of) HDR files, float interface for preferred access to them - 1.01 fix bug: possible bug in handling right-side up bmps... not sure - fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all - 1.00 interface to zlib that skips zlib header - 0.99 correct handling of alpha in palette - 0.98 TGA loader by lonesock; dynamically add loaders (untested) - 0.97 jpeg errors on too large a file; also catch another malloc failure - 0.96 fix detection of invalid v value - particleman@mollyrocket forum - 0.95 during header scan, seek to markers in case of padding - 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same - 0.93 handle jpegtran output; verbose errors - 0.92 read 4,8,16,24,32-bit BMP files of several formats - 0.91 output 24-bit Windows 3.0 BMP files - 0.90 fix a few more warnings; bump version number to approach 1.0 - 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd - 0.60 fix compiling as c++ - 0.59 fix warnings: merge Dave Moore's -Wall fixes - 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian - 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available - 0.56 fix bug: zlib uncompressed mode len vs. nlen - 0.55 fix bug: restart_interval not initialized to 0 - 0.54 allow NULL for 'int *comp' - 0.53 fix bug in png 3->4; speedup png decoding - 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments - 0.51 obey req_comp requests, 1-component jpegs return as 1-component, - on 'test' only check type, not whether we support this variant - 0.50 (2006-11-19) - first released version -*/ - - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index ffe39f9f7..54273b90c 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -32,7 +32,7 @@ PROJECT_NAME = Assimp # This could be handy for archiving the generated documentation or # if some version control system is used. -PROJECT_NUMBER = "v4.1. (December 2018)" +PROJECT_NUMBER = "v5.0.1. (December 2020)" # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer @@ -197,7 +197,7 @@ SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. -TAB_SIZE = 8 +TAB_SIZE = 4 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". @@ -636,7 +636,7 @@ WARN_IF_DOC_ERROR = YES # wrong or incomplete parameter documentation, but not about the absence of # documentation. -WARN_NO_PARAMDOC = NO +WARN_NO_PARAMDOC = YES # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text @@ -663,11 +663,7 @@ WARN_LOGFILE = # with spaces. INPUT = @doxy_main_page@ \ - @PROJECT_SOURCE_DIR@ \ - @PROJECT_BINARY_DIR@ \ - @PROJECT_SOURCE_DIR@/include/ \ - @PROJECT_SOURCE_DIR@/doc/dox.h \ - @PROJECT_SOURCE_DIR@/code/BaseImporter.h + @PROJECT_SOURCE_DIR@/include/ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is @@ -690,28 +686,10 @@ FILE_PATTERNS = *.c \ *.cxx \ *.cpp \ *.c++ \ - *.d \ - *.java \ - *.ii \ - *.ixx \ - *.ipp \ - *.i++ \ *.inl \ *.h \ - *.hh \ - *.hxx \ *.hpp \ - *.h++ \ - *.idl \ - *.odl \ - *.cs \ - *.php \ - *.php3 \ - *.inc \ - *.m \ - *.mm \ - *.dox \ - *.py + *.dox # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. @@ -725,7 +703,7 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = irrXML.h +EXCLUDE = contrib/* # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -739,8 +717,7 @@ EXCLUDE_SYMLINKS = NO # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* -EXCLUDE_PATTERNS = */.svn/* \ - */.svn +EXCLUDE_PATTERNS = */.git/* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -901,7 +878,7 @@ IGNORE_PREFIX = # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. -GENERATE_HTML = YES +GENERATE_HTML = NO # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be @@ -1484,7 +1461,7 @@ MAN_LINKS = NO # generate an XML file that captures the structure of # the code including all documentation. -GENERATE_XML = NO +GENERATE_XML = YES # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be diff --git a/doc/dox.h b/doc/dox.h index a4516dc7a..409e775d4 100644 --- a/doc/dox.h +++ b/doc/dox.h @@ -90,8 +90,8 @@ but not all of them are *open-source*. If there's an accompagning '\source @section main_install Installation assimp can be used in two ways: linking against the pre-built libraries or building the library on your own. The former -option is the easiest, but the assimp distribution contains pre-built libraries only for Visual C++ 2013, 2015 and 2017. -For other compilers you'll have to build assimp for yourself. Which is hopefully as hassle-free as the other way, but +option is the easiest, but the assimp distribution contains pre-built libraries only for Visual C++ 2013, 2015 and 2017. +For other compilers you'll have to build assimp for yourself. Which is hopefully as hassle-free as the other way, but needs a bit more work. Both ways are described at the @link install Installation page. @endlink If you want to use assimp on Ubuntu you can install it via the following command: @@ -145,7 +145,7 @@ to your include paths (Menu->Extras->Options->Projects and Solutions-&g and the assimp/lib/<Compiler> path to your linker paths (Menu->Extras->Options->Projects and Solutions->VC++ Directories->Library files). This is necessary only once to setup all paths inside you IDE. -To use the library in your C++ project you can simply generate a project file via cmake. One way is to add the assimp-folder +To use the library in your C++ project you can simply generate a project file via cmake. One way is to add the assimp-folder as a subdirectory via the cmake-command @code @@ -158,7 +158,7 @@ Now just add the assimp-dependency to your application: TARGET_LINK_LIBRARIES(my_game assimp) @endcode -If done correctly you should now be able to compile, link, run and use the application. +If done correctly you should now be able to compile, link, run and use the application. @section install_own Building the library from scratch @@ -170,7 +170,7 @@ to build the library just open a command-prompt / bash, navigate into the repo-f cmake CMakeLists.txt @endcode -A project-file of your default make-system ( like gnu-make on linux or Visual-Studio on Windows ) will be generated. +A project-file of your default make-system ( like gnu-make on linux or Visual-Studio on Windows ) will be generated. Run the build and you are done. You can find the libs at assimp/lib and the dll's / so's at bin. @section assimp_dll Windows DLL Build @@ -496,10 +496,10 @@ X3 Y3 Z3 T3 @endcode with (X1, X2, X3) being the local X base vector, (Y1, Y2, Y3) being the local Y base vector, (Z1, Z2, Z3) being the local Z base vector and (T1, T2, T3) being the -offset of the local origin (the translational part). +offset of the local origin (the translational part). All matrices in the library use row-major storage order. That means that the matrix elements are -stored row-by-row, i.e. they end up like this in memory: -[X1, Y1, Z1, T1, X2, Y2, Z2, T2, X3, Y3, Z3, T3, 0, 0, 0, 1]. +stored row-by-row, i.e. they end up like this in memory: +[X1, Y1, Z1, T1, X2, Y2, Z2, T2, X3, Y3, Z3, T3, 0, 0, 0, 1]. Note that this is neither the OpenGL format nor the DirectX format, because both of them specify the matrix layout such that the translational part occupies three consecutive addresses in memory (so those @@ -1498,7 +1498,7 @@ Just copy'n'paste the template from Appendix A and adapt it for your needs. with DefaultLogger::get()->[error, warn, debug, info].
  • -Make sure that your loader compiles against all build configurations on all supported platforms. You can use our CI-build to check several platforms +Make sure that your loader compiles against all build configurations on all supported platforms. You can use our CI-build to check several platforms like Windows and Linux ( 32 bit and 64 bit ).
  • diff --git a/doc/dox_cmd.h b/doc/dox_cmd.h index 78e755d56..79ea3a861 100644 --- a/doc/dox_cmd.h +++ b/doc/dox_cmd.h @@ -12,7 +12,7 @@ @section intro Introduction -This document describes the usage of assimp's command line tools. +This document describes the usage of assimp's command line tools. This is *not* the SDK reference and programming-related stuff is not covered here.

    NOTE: For simplicity, the following sections are written with Windows in mind. However @@ -29,7 +29,7 @@ assimp [command] [parameters] The following commands are available: - + @@ -184,7 +184,7 @@ Generate a text or binary dump of a model. This is the core component of Assimp' regression test suite but it could also be useful for other developers to quickly examine the contents of a model. Note that text dumps are not intended to be used as intermediate format, Assimp is not able to read them again, nor is the file format -stable or well-defined. It may change with every revision without notice. +stable or well-defined. It may change with every revision without notice. Binary dumps (*.assbin) are backwards- and forwards-compatible.

    Syntax:

    @@ -199,7 +199,7 @@ assimp dump [] [-b] [-s] [common parameters]

    model

    -Required. Relative or absolute path to the input model. +Required. Relative or absolute path to the input model.

    @@ -220,7 +220,7 @@ The long form of this parameter is --binary.
    Optional. If this switch is specified, the dump is shortened to include only min/max values for all vertex components and animation channels. The resulting -file is much smaller, but the original model can't be reconstructed from it. This is +file is much smaller, but the original model can't be reconstructed from it. This is used by Assimp's regression test suite, comparing those minidumps provides a fast way to verify whether a loader works correctly or not. The long form of this parameter is --short. @@ -229,7 +229,7 @@ The long form of this parameter is --short.

    common parameters

    -Optional. Import configuration & postprocessing. +Optional. Import configuration & postprocessing. See the @link common common parameters page @endlink for more information.

    @@ -248,7 +248,7 @@ The log output is included with the dump. @code assimp dump files\*.* -assimp dump files\*.* +assimp dump files\*.* @endcode Dumps all loadable model files in the 'files' subdir. The output dumps are named @@ -275,14 +275,14 @@ assimp extract [] [-t] [-f] [-ba] [-s] [common parameters]

    model

    -Required. Relative or absolute path to the input model. +Required. Relative or absolute path to the input model.

    out

    Optional. Relative or absolute path to write the output images to. If the file name is omitted the output images are named
    -The suffix _img<n> is appended to the file name if the -s switch is not specified +The suffix _img<n> is appended to the file name if the -s switch is not specified (where <n> is the zero-based index of the texture in the model file).
    The output file format is determined from the given file extension. Supported @@ -296,7 +296,7 @@ written in their native file format (e.g. jpg).

    -t<n>

    -Optional. Specifies the (zero-based) index of the embedded texture to be extracted from +Optional. Specifies the (zero-based) index of the embedded texture to be extracted from the model. If this option is *not* specified all textures found are exported. The long form of this parameter is --texture=<n>.

    @@ -348,8 +348,8 @@ imported data structure and writes it to test_img0.bmp. @code -assimp extract files\*.mdl *.bmp -assimp extract files\*.mdl *.bmp +assimp extract files\*.mdl *.bmp +assimp extract files\*.mdl *.bmp @endcode Extracts all embedded textures from all loadable .mdl files in the 'files' subdirectory @@ -361,10 +361,10 @@ and writes them to bitmaps which are named _img.bmp /** @page common Common parameters -The parameters described on this page are commonly used by almost every assimp command. They +The parameters described on this page are commonly used by almost every assimp command. They specify how the library will postprocess the imported data. This is done by several configurable pipeline stages, called 'post processing steps'. Below you can find a list -of all supported steps along with short descriptions of what they're doing.
    Programmers: +of all supported steps along with short descriptions of what they're doing.
    Programmers: more information can be found in the aiPostProcess.h header.
    @link version version @endlink Retrieve the current version of assimp
    @@ -376,7 +376,7 @@ more information can be found in the aiPostProcess.h header. - @@ -428,7 +428,7 @@ more information can be found in the aiPostProcess.h header. - @@ -515,7 +515,7 @@ For convenience some default postprocessing configurations are provided. The corresponding command line parameter is -c<name> (or --config=<name>).
    -ptv --pretransform-verticesMove all vertices into worldspace and collapse the scene graph. Animation data is lost. + Move all vertices into worldspace and collapse the scene graph. Animation data is lost. This is intended for applications which don't support scenegraph-oriented rendering.
    -icl --improve-cache-localityImprove the cache locality of the vertex buffer by reordering the index buffer + Improve the cache locality of the vertex buffer by reordering the index buffer to achieve a lower ACMR (average post-transform vertex cache miss ratio)
    - + @@ -543,7 +543,7 @@ The corresponding command line parameter is -c<name> (or --co There are also some common flags to customize Assimp's logging behaviour:
    Name Description
    - + @@ -558,7 +558,7 @@ There are also some common flags to customize Assimp's logging behaviour: -
    Name Description
    -v or --verboseEnables verbose logging. Debug messages will be produced too. This might + Enables verbose logging. Debug messages will be produced too. This might decrease loading performance and result in *very* long logs ... use with caution if you experience strange issues.
    diff --git a/fuzz/assimp_fuzzer.cc b/fuzz/assimp_fuzzer.cc index b65ee0236..33748c10f 100644 --- a/fuzz/assimp_fuzzer.cc +++ b/fuzz/assimp_fuzzer.cc @@ -54,6 +54,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t dataSize) { aiProcessPreset_TargetRealtime_Quality, nullptr ); aiDetachLogStream(&stream); - + return 0; } diff --git a/include/assimp/BaseImporter.h b/include/assimp/BaseImporter.h index 38bec1afd..54b5daac1 100644 --- a/include/assimp/BaseImporter.h +++ b/include/assimp/BaseImporter.h @@ -154,7 +154,7 @@ public: /** Returns the exception of the last exception that occurred. * Note: Exceptions are not the only source of error details, so GetErrorText * should be consulted too. - * @return The last exception that occurred. + * @return The last exception that occurred. */ const std::exception_ptr& GetException() const { return m_Exception; diff --git a/include/assimp/Compiler/poppack1.h b/include/assimp/Compiler/poppack1.h index a8e9a3c7e..ff501bc0c 100644 --- a/include/assimp/Compiler/poppack1.h +++ b/include/assimp/Compiler/poppack1.h @@ -1,7 +1,7 @@ // =============================================================================== -// May be included multiple times - resets structure packing to the defaults -// for all supported compilers. Reverts the changes made by #include +// May be included multiple times - resets structure packing to the defaults +// for all supported compilers. Reverts the changes made by #include // // Currently this works on the following compilers: // MSVC 7,8,9 diff --git a/include/assimp/Compiler/pushpack1.h b/include/assimp/Compiler/pushpack1.h index 2a5e2dfe6..b32ed172c 100644 --- a/include/assimp/Compiler/pushpack1.h +++ b/include/assimp/Compiler/pushpack1.h @@ -1,7 +1,7 @@ // =============================================================================== -// May be included multiple times - sets structure packing to 1 +// May be included multiple times - sets structure packing to 1 // for all supported compilers. #include reverts the changes. // // Currently this works on the following compilers: @@ -37,7 +37,7 @@ #if defined(_MSC_VER) // C4103: Packing was changed after the inclusion of the header, probably missing #pragma pop -# pragma warning (disable : 4103) +# pragma warning (disable : 4103) #endif #define AI_PUSHPACK_IS_DEFINED diff --git a/include/assimp/Exceptional.h b/include/assimp/Exceptional.h index 98e2a3321..1bf399cbc 100644 --- a/include/assimp/Exceptional.h +++ b/include/assimp/Exceptional.h @@ -59,7 +59,7 @@ using std::runtime_error; class ASSIMP_API DeadlyErrorBase : public runtime_error { protected: DeadlyErrorBase(Assimp::Formatter::format f); - + template DeadlyErrorBase(Assimp::Formatter::format f, U&& u, T&&... args) : DeadlyErrorBase(std::move(f << std::forward(u)), std::forward(args)...) {} diff --git a/include/assimp/Exporter.hpp b/include/assimp/Exporter.hpp index 8e78ac954..6ab35a8f0 100644 --- a/include/assimp/Exporter.hpp +++ b/include/assimp/Exporter.hpp @@ -390,7 +390,7 @@ public: * @see SetPropertyInteger() */ bool SetPropertyMatrix(const char *szName, const aiMatrix4x4 &sValue); - + bool SetPropertyCallback(const char *szName, const std::function &f); // ------------------------------------------------------------------- diff --git a/include/assimp/IOStreamBuffer.h b/include/assimp/IOStreamBuffer.h index ee4ac0953..c3b7327ff 100644 --- a/include/assimp/IOStreamBuffer.h +++ b/include/assimp/IOStreamBuffer.h @@ -81,7 +81,7 @@ public: /// @brief Returns the file-size. /// @return The file-size. size_t size() const; - + /// @brief Returns the cache size. /// @return The cache size. size_t cacheSize() const; @@ -278,7 +278,7 @@ bool IOStreamBuffer::getNextDataLine( std::vector &buffer, T continuationT } } } - + buffer[ i ] = '\n'; ++m_cachePos; @@ -334,7 +334,7 @@ template AI_FORCE_INLINE bool IOStreamBuffer::getNextBlock( std::vector &buffer) { // Return the last block-value if getNextLine was used before - if ( 0 != m_cachePos ) { + if ( 0 != m_cachePos ) { buffer = std::vector( m_cache.begin() + m_cachePos, m_cache.end() ); m_cachePos = 0; } else { diff --git a/include/assimp/IOSystem.hpp b/include/assimp/IOSystem.hpp index 76f876440..7be373cf1 100644 --- a/include/assimp/IOSystem.hpp +++ b/include/assimp/IOSystem.hpp @@ -62,9 +62,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "types.h" #ifdef _WIN32 -# include -# include -# include +# include +# include +# include #else # include # include @@ -84,7 +84,7 @@ namespace Assimp { * to the importer library. If you implement this interface, you also want to * supply a custom implementation for IOStream. * - * @see Importer::SetIOHandler() + * @see Importer::SetIOHandler() */ class ASSIMP_API IOSystem #ifndef SWIG diff --git a/include/assimp/Logger.hpp b/include/assimp/Logger.hpp index 3ca4a6cb2..aa7ffba7c 100644 --- a/include/assimp/Logger.hpp +++ b/include/assimp/Logger.hpp @@ -99,8 +99,8 @@ public: virtual ~Logger(); // ---------------------------------------------------------------------- - /** @brief Writes a info message - * @param message Info message*/ + /** @brief Writes a debug message + * @param message Debug message*/ void debug(const char* message); template @@ -109,10 +109,10 @@ public: } // ---------------------------------------------------------------------- - /** @brief Writes a debug message + /** @brief Writes a debug message * @param message Debug message*/ void verboseDebug(const char* message); - + template void verboseDebug(T&&... args) { verboseDebug(formatMessage(std::forward(args)...).c_str()); @@ -140,7 +140,7 @@ public: // ---------------------------------------------------------------------- /** @brief Writes an error message - * @param message Info message*/ + * @param message Error message*/ void error(const char* message); template diff --git a/include/assimp/MemoryIOWrapper.h b/include/assimp/MemoryIOWrapper.h index 86dc5ea60..24f6a85d1 100644 --- a/include/assimp/MemoryIOWrapper.h +++ b/include/assimp/MemoryIOWrapper.h @@ -57,7 +57,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include namespace Assimp { - + #define AI_MEMORYIO_MAGIC_FILENAME "$$$___magic___$$$" #define AI_MEMORYIO_MAGIC_FILENAME_LENGTH 17 @@ -85,7 +85,7 @@ public: size_t Read(void* pvBuffer, size_t pSize, size_t pCount) { ai_assert(nullptr != pvBuffer); ai_assert(0 != pSize); - + const size_t cnt = std::min( pCount, (length-pos) / pSize); const size_t ofs = pSize * cnt; @@ -209,7 +209,7 @@ public: return existing_io ? existing_io->ComparePaths(one, second) : false; } - bool PushDirectory( const std::string &path ) override { + bool PushDirectory( const std::string &path ) override { return existing_io ? existing_io->PushDirectory(path) : false; } diff --git a/include/assimp/SmallVector.h b/include/assimp/SmallVector.h index bcb8482a9..fb78f5a97 100644 --- a/include/assimp/SmallVector.h +++ b/include/assimp/SmallVector.h @@ -53,17 +53,17 @@ Based on CppCon 2016: Chandler Carruth "High Performance Code 201: Hybrid Data S namespace Assimp { // -------------------------------------------------------------------------------------------- -/// @brief Small vector with inplace storage. +/// @brief Small vector with inplace storage. /// /// Reduces heap allocations when list is shorter. It uses a small array for a dedicated size. -/// When the growing gets bigger than this small cache a dynamic growing algorithm will be +/// When the growing gets bigger than this small cache a dynamic growing algorithm will be /// used. // -------------------------------------------------------------------------------------------- template class SmallVector { public: /// @brief The default class constructor. - SmallVector() : + SmallVector() : mStorage(mInplaceStorage), mSize(0), mCapacity(Capacity) { @@ -84,7 +84,7 @@ public: mStorage[mSize++] = item; return; } - + push_back_and_grow(item); } diff --git a/include/assimp/SmoothingGroups.inl b/include/assimp/SmoothingGroups.inl index a32626e00..08c2dfa53 100644 --- a/include/assimp/SmoothingGroups.inl +++ b/include/assimp/SmoothingGroups.inl @@ -7,8 +7,8 @@ Copyright (c) 2006-2021, 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 +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 @@ -25,16 +25,16 @@ conditions are met: 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 +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 +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 +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 +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. --------------------------------------------------------------------------- */ @@ -77,7 +77,7 @@ void ComputeNormalsWithSmoothingsGroups(MeshWithSmoothingGroups& sMesh) sMesh.mNormals[face.mIndices[c]] = vNor; } - // calculate the position bounds so we have a reliable epsilon to check position differences against + // calculate the position bounds so we have a reliable epsilon to check position differences against aiVector3D minVec( 1e10f, 1e10f, 1e10f), maxVec( -1e10f, -1e10f, -1e10f); for( unsigned int a = 0; a < sMesh.mPositions.size(); a++) { @@ -91,7 +91,7 @@ void ComputeNormalsWithSmoothingsGroups(MeshWithSmoothingGroups& sMesh) const float posEpsilon = (maxVec - minVec).Length() * 1e-5f; std::vector avNormals; avNormals.resize(sMesh.mNormals.size()); - + // now generate the spatial sort tree SGSpatialSort sSort; for( typename std::vector::iterator i = sMesh.mFaces.begin(); diff --git a/include/assimp/XmlParser.h b/include/assimp/XmlParser.h index 18d48f337..9c2dc419e 100644 --- a/include/assimp/XmlParser.h +++ b/include/assimp/XmlParser.h @@ -94,7 +94,7 @@ using XmlAttribute = pugi::xml_attribute; /// } /// } /// @endcode -/// @tparam TNodeType +/// @tparam TNodeType template class TXmlParser { public: @@ -123,7 +123,7 @@ public: /// @brief Will search for a child-node by its name /// @param name [in] The name of the child-node. - /// @return The node instance or nullptr, if nothing was found. + /// @return The node instance or nullptr, if nothing was found. TNodeType *findNode(const std::string &name) { if (name.empty()) { return nullptr; @@ -162,12 +162,12 @@ public: mData.resize(len + 1); memset(&mData[0], '\0', len + 1); stream->Read(&mData[0], 1, len); - + mDoc = new pugi::xml_document(); pugi::xml_parse_result parse_result = mDoc->load_string(&mData[0], pugi::parse_full); if (parse_result.status == pugi::status_ok) { return true; - } + } ASSIMP_LOG_DEBUG("Error while parse xml.", std::string(parse_result.description()), " @ ", parse_result.offset); @@ -457,7 +457,7 @@ public: } private: - XmlNode &mParent; + XmlNode &mParent; std::vector mNodes; size_t mIndex; }; diff --git a/include/assimp/ai_assert.h b/include/assimp/ai_assert.h index 6736b24c9..b377b6e8b 100644 --- a/include/assimp/ai_assert.h +++ b/include/assimp/ai_assert.h @@ -57,7 +57,7 @@ namespace Assimp #else # define ai_assert(expression) -# define ai_assert_entry() +# define ai_assert_entry() #endif // ASSIMP_BUILD_DEBUG #endif // AI_ASSERT_H_INC diff --git a/include/assimp/anim.h b/include/assimp/anim.h index 79841a537..dcd054d1e 100644 --- a/include/assimp/anim.h +++ b/include/assimp/anim.h @@ -39,7 +39,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------- */ -/** +/** * @file anim.h * @brief Defines the data structures in which the imported animations * are returned. @@ -478,11 +478,11 @@ struct aiAnimation { namespace Assimp { // --------------------------------------------------------------------------- -/** +/** * @brief CPP-API: Utility class to simplify interpolations of various data types. * * The type of interpolation is chosen automatically depending on the - * types of the arguments. + * types of the arguments. */ template struct Interpolator { diff --git a/include/assimp/cimport.h b/include/assimp/cimport.h index b4a172742..d660eebb1 100644 --- a/include/assimp/cimport.h +++ b/include/assimp/cimport.h @@ -894,7 +894,7 @@ ASSIMP_API float aiMatrix3Determinant( // -------------------------------------------------------------------------------- /** Get a 3x3 rotation matrix around the Z axis. - * @param mat Receives the output matrix + * @param mat Receives the output matrix * @param angle Rotation angle, in radians */ ASSIMP_API void aiMatrix3RotationZ( @@ -903,7 +903,7 @@ ASSIMP_API void aiMatrix3RotationZ( // -------------------------------------------------------------------------------- /** Returns a 3x3 rotation matrix for a rotation around an arbitrary axis. - * @param mat Receives the output matrix + * @param mat Receives the output matrix * @param axis Rotation axis, should be a normalized vector * @param angle Rotation angle, in radians */ @@ -914,7 +914,7 @@ ASSIMP_API void aiMatrix3FromRotationAroundAxis( // -------------------------------------------------------------------------------- /** Get a 3x3 translation matrix. - * @param mat Receives the output matrix + * @param mat Receives the output matrix * @param translation The translation vector */ ASSIMP_API void aiMatrix3Translation( @@ -923,7 +923,7 @@ ASSIMP_API void aiMatrix3Translation( // -------------------------------------------------------------------------------- /** Create a 3x3 matrix that rotates one vector to another vector. - * @param mat Receives the output matrix + * @param mat Receives the output matrix * @param from Vector to rotate from * @param to Vector to rotate to */ @@ -1059,7 +1059,7 @@ ASSIMP_API void aiMatrix4DecomposeNoScaling( // -------------------------------------------------------------------------------- /** Creates a 4x4 matrix from a set of euler angles. - * @param mat Receives the output matrix + * @param mat Receives the output matrix * @param x Rotation angle for the x-axis, in radians * @param y Rotation angle for the y-axis, in radians * @param z Rotation angle for the z-axis, in radians @@ -1137,7 +1137,7 @@ ASSIMP_API void aiMatrix4FromTo( // -------------------------------------------------------------------------------- /** Create a Quaternion from euler angles. - * @param q Receives the output quaternion + * @param q Receives the output quaternion * @param x Rotation angle for the x-axis, in radians * @param y Rotation angle for the y-axis, in radians * @param z Rotation angle for the z-axis, in radians diff --git a/include/assimp/defs.h b/include/assimp/defs.h index d61fd7901..8d1011a28 100644 --- a/include/assimp/defs.h +++ b/include/assimp/defs.h @@ -161,7 +161,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #pragma warning(disable : 4251) #endif /* Force the compiler to inline a function, if possible */ - #define AI_FORCE_INLINE inline + #define AI_FORCE_INLINE inline /* Tells the compiler that a function never returns. Used in code analysis * to skip dead paths (e.g. after an assertion evaluated to false). */ diff --git a/include/assimp/fast_atof.h b/include/assimp/fast_atof.h index aea793f35..43bbbff64 100644 --- a/include/assimp/fast_atof.h +++ b/include/assimp/fast_atof.h @@ -194,7 +194,7 @@ uint64_t strtoul10_64( const char* in, const char** out=0, unsigned int* max_ino if ( *in < '0' || *in > '9' ) { // The string is known to be bad, so don't risk printing the whole thing. - throw ExceptionType("The string \"", ai_str_toprintable(in, 30), "\" cannot be converted into a value." ); + throw ExceptionType("The string \"", ai_str_toprintable(in, (int)strlen(in)), "\" cannot be converted into a value." ); } for ( ;; ) { @@ -294,7 +294,7 @@ const char* fast_atoreal_move(const char* c, Real& out, bool check_comma = true) if (!(c[0] >= '0' && c[0] <= '9') && !((c[0] == '.' || (check_comma && c[0] == ',')) && c[1] >= '0' && c[1] <= '9')) { // The string is known to be bad, so don't risk printing the whole thing. - throw ExceptionType("Cannot parse string \"", ai_str_toprintable(c, 30), + throw ExceptionType("Cannot parse string \"", ai_str_toprintable(c, (int)strlen(c)), "\" as a real number: does not start with digit " "or decimal point followed by digit."); } diff --git a/include/assimp/light.h b/include/assimp/light.h index bc37de43a..48efdaebd 100644 --- a/include/assimp/light.h +++ b/include/assimp/light.h @@ -257,7 +257,7 @@ struct aiLight #ifdef __cplusplus } -#endif +#endif #endif // !! AI_LIGHT_H_INC diff --git a/include/assimp/material.h b/include/assimp/material.h index 08c0491c0..250ad90d5 100644 --- a/include/assimp/material.h +++ b/include/assimp/material.h @@ -144,7 +144,7 @@ enum aiTextureMapMode { enum aiTextureMapping { /** The mapping coordinates are taken from an UV channel. * - * The #AI_MATKEY_UVWSRC key specifies from which UV channel + * #AI_MATKEY_UVWSRC property specifies from which UV channel * the texture coordinates are to be taken from (remember, * meshes can have more than one UV channel). */ @@ -194,19 +194,23 @@ enum aiTextureType { */ aiTextureType_NONE = 0, - /** LEGACY API MATERIALS - * Legacy refers to materials which + /** LEGACY API MATERIALS + * Legacy refers to materials which * Were originally implemented in the specifications around 2000. * These must never be removed, as most engines support them. */ /** The texture is combined with the result of the diffuse * lighting equation. + * OR + * PBR Specular/Glossiness */ aiTextureType_DIFFUSE = 1, /** The texture is combined with the result of the specular * lighting equation. + * OR + * PBR Specular/Glossiness */ aiTextureType_SPECULAR = 2, @@ -288,6 +292,32 @@ enum aiTextureType { aiTextureType_DIFFUSE_ROUGHNESS = 16, aiTextureType_AMBIENT_OCCLUSION = 17, + /** PBR Material Modifiers + * Some modern renderers have further PBR modifiers that may be overlaid + * on top of the 'base' PBR materials for additional realism. + * These use multiple texture maps, so only the base type is directly defined + */ + + /** Sheen + * Generally used to simulate textiles that are covered in a layer of microfibers + * eg velvet + * https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_sheen + */ + aiTextureType_SHEEN = 19, + + /** Clearcoat + * Simulates a layer of 'polish' or 'laquer' layered on top of a PBR substrate + * https://autodesk.github.io/standard-surface/#closures/coating + * https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat + */ + aiTextureType_CLEARCOAT = 20, + + /** Transmission + * Simulates transmission through the surface + * May include further information such as wall thickness + */ + aiTextureType_TRANSMISSION = 21, + /** Unknown texture * * A texture reference that does not match any of the definitions @@ -309,6 +339,8 @@ ASSIMP_API const char *TextureTypeToString(enum aiTextureType in); // --------------------------------------------------------------------------- /** @brief Defines all shading models supported by the library + * + * Property: #AI_MATKEY_SHADING_MODEL * * The list of shading modes has been taken from Blender. * See Blender documentation for more information. The API does @@ -318,6 +350,7 @@ ASSIMP_API const char *TextureTypeToString(enum aiTextureType in); * Again, this value is just a hint. Assimp tries to select the shader whose * most common implementation matches the original rendering results of the * 3D modeler which wrote a particular model as closely as possible. + * */ enum aiShadingMode { /** Flat shading. Shading is done on per-face base, @@ -364,13 +397,28 @@ enum aiShadingMode { aiShadingMode_CookTorrance = 0x8, /** No shading at all. Constant light influence of 1.0. + * Also known as "Unlit" */ aiShadingMode_NoShading = 0x9, + aiShadingMode_Unlit = aiShadingMode_NoShading, // Alias /** Fresnel shading */ aiShadingMode_Fresnel = 0xa, + /** Physically-Based Rendering (PBR) shading using + * Bidirectional scattering/reflectance distribution function (BSDF/BRDF) + * There are multiple methods under this banner, and model files may provide + * data for more than one PBR-BRDF method. + * Applications should use the set of provided properties to determine which + * of their preferred PBR rendering methods are likely to be available + * eg: + * - If AI_MATKEY_METALLIC_FACTOR is set, then a Metallic/Roughness is available + * - If AI_MATKEY_GLOSSINESS_FACTOR is set, then a Specular/Glossiness is available + * Note that some PBR methods allow layering of techniques + */ + aiShadingMode_PBR_BRDF = 0xb, + #ifndef SWIG _aiShadingMode_Force32Bit = INT_MAX #endif @@ -922,12 +970,66 @@ extern "C" { // --------------------------------------------------------------------------- // PBR material support +// -------------------- +// Properties defining PBR rendering techniques #define AI_MATKEY_USE_COLOR_MAP "$mat.useColorMap", 0, 0 + +// Metallic/Roughness Workflow +// --------------------------- +// Base RGBA color factor. Will be multiplied by final base color texture values if extant +// Note: Importers may choose to copy this into AI_MATKEY_COLOR_DIFFUSE for compatibility +// with renderers and formats that do not support Metallic/Roughness PBR #define AI_MATKEY_BASE_COLOR "$clr.base", 0, 0 +#define AI_MATKEY_BASE_COLOR_TEXTURE aiTextureType_BASE_COLOR, 0 #define AI_MATKEY_USE_METALLIC_MAP "$mat.useMetallicMap", 0, 0 +// Metallic factor. 0.0 = Full Dielectric, 1.0 = Full Metal #define AI_MATKEY_METALLIC_FACTOR "$mat.metallicFactor", 0, 0 +#define AI_MATKEY_METALLIC_TEXTURE aiTextureType_METALNESS, 0 #define AI_MATKEY_USE_ROUGHNESS_MAP "$mat.useRoughnessMap", 0, 0 +// Roughness factor. 0.0 = Perfectly Smooth, 1.0 = Completely Rough #define AI_MATKEY_ROUGHNESS_FACTOR "$mat.roughnessFactor", 0, 0 +#define AI_MATKEY_ROUGHNESS_TEXTURE aiTextureType_DIFFUSE_ROUGHNESS, 0 + +// Specular/Glossiness Workflow +// --------------------------- +// Diffuse/Albedo Color. Note: Pure Metals have a diffuse of {0,0,0} +// AI_MATKEY_COLOR_DIFFUSE +// Specular Color. +// Note: Metallic/Roughness may also have a Specular Color +// AI_MATKEY_COLOR_SPECULAR +#define AI_MATKEY_SPECULAR_FACTOR "$mat.specularFactor", 0, 0 +// Glossiness factor. 0.0 = Completely Rough, 1.0 = Perfectly Smooth +#define AI_MATKEY_GLOSSINESS_FACTOR "$mat.glossinessFactor", 0, 0 + +// Sheen +// ----- +// Sheen base RGB color. Default {0,0,0} +#define AI_MATKEY_SHEEN_COLOR_FACTOR "$clr.sheen.factor", 0, 0 +// Sheen Roughness Factor. +#define AI_MATKEY_SHEEN_ROUGHNESS_FACTOR "$mat.sheen.roughnessFactor", 0, 0 +#define AI_MATKEY_SHEEN_COLOR_TEXTURE aiTextureType_SHEEN, 0 +#define AI_MATKEY_SHEEN_ROUGHNESS_TEXTURE aiTextureType_SHEEN, 1 + +// Clearcoat +// --------- +// Clearcoat layer intensity. 0.0 = none (disabled) +#define AI_MATKEY_CLEARCOAT_FACTOR "$mat.clearcoat.factor", 0, 0 +#define AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR "$mat.clearcoat.roughnessFactor", 0, 0 +#define AI_MATKEY_CLEARCOAT_TEXTURE aiTextureType_CLEARCOAT, 0 +#define AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE aiTextureType_CLEARCOAT, 1 +#define AI_MATKEY_CLEARCOAT_NORMAL_TEXTURE aiTextureType_CLEARCOAT, 2 + +// Transmission +// ------------ +// https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission +// Base percentage of light transmitted through the surface. 0.0 = Opaque, 1.0 = Fully transparent +#define AI_MATKEY_TRANSMISSION_FACTOR "$mat.transmission.factor", 0, 0 +// Texture defining percentage of light transmitted through the surface. +// Multiplied by AI_MATKEY_TRANSMISSION_FACTOR +#define AI_MATKEY_TRANSMISSION_TEXTURE aiTextureType_TRANSMISSION, 0 + +// Emissive +// -------- #define AI_MATKEY_USE_EMISSIVE_MAP "$mat.useEmissiveMap", 0, 0 #define AI_MATKEY_EMISSIVE_INTENSITY "$mat.emissiveIntensity", 0, 0 #define AI_MATKEY_USE_AO_MAP "$mat.useAOMap", 0, 0 @@ -1397,8 +1499,6 @@ ASSIMP_API C_ENUM aiReturn aiGetMaterialFloatArray( ai_real *pOut, unsigned int *pMax); -#ifdef __cplusplus - // --------------------------------------------------------------------------- /** @brief Retrieve a single float property with a specific key from the material. * @@ -1418,7 +1518,7 @@ ASSIMP_API C_ENUM aiReturn aiGetMaterialFloatArray( * @return Specifies whether the key has been found. If not, the output * float remains unmodified.*/ // --------------------------------------------------------------------------- -inline aiReturn aiGetMaterialFloat(const aiMaterial *pMat, +inline aiReturn aiGetMaterialFloat(const C_STRUCT aiMaterial *pMat, const char *pKey, unsigned int type, unsigned int index, @@ -1426,14 +1526,6 @@ inline aiReturn aiGetMaterialFloat(const aiMaterial *pMat, return aiGetMaterialFloatArray(pMat, pKey, type, index, pOut, (unsigned int *)0x0); } -#else - -// Use our friend, the C preprocessor -#define aiGetMaterialFloat (pMat, type, index, pKey, pOut) \ - aiGetMaterialFloatArray(pMat, type, index, pKey, pOut, NULL) - -#endif //!__cplusplus - // --------------------------------------------------------------------------- /** @brief Retrieve an array of integer values with a specific key * from a material @@ -1446,8 +1538,6 @@ ASSIMP_API C_ENUM aiReturn aiGetMaterialIntegerArray(const C_STRUCT aiMaterial * int *pOut, unsigned int *pMax); -#ifdef __cplusplus - // --------------------------------------------------------------------------- /** @brief Retrieve an integer property with a specific key from a material * @@ -1461,14 +1551,6 @@ inline aiReturn aiGetMaterialInteger(const C_STRUCT aiMaterial *pMat, return aiGetMaterialIntegerArray(pMat, pKey, type, index, pOut, (unsigned int *)0x0); } -#else - -// use our friend, the C preprocessor -#define aiGetMaterialInteger (pMat, type, index, pKey, pOut) \ - aiGetMaterialIntegerArray(pMat, type, index, pKey, pOut, NULL) - -#endif //!__cplusplus - // --------------------------------------------------------------------------- /** @brief Retrieve a color value from the material property table * diff --git a/include/assimp/matrix4x4.h b/include/assimp/matrix4x4.h index c929ef2a9..6caf7686c 100644 --- a/include/assimp/matrix4x4.h +++ b/include/assimp/matrix4x4.h @@ -230,7 +230,7 @@ public: * @param out Receives the output matrix * @return Reference to the output matrix */ - static aiMatrix4x4t& Translation( const aiVector3t& v, + static aiMatrix4x4t& Translation( const aiVector3t& v, aiMatrix4x4t& out); // ------------------------------------------------------------------- diff --git a/include/assimp/matrix4x4.inl b/include/assimp/matrix4x4.inl index 88315ac28..c1dd87b65 100644 --- a/include/assimp/matrix4x4.inl +++ b/include/assimp/matrix4x4.inl @@ -421,7 +421,7 @@ void aiMatrix4x4t::Decompose(aiVector3t& pScaling, aiVector3tmArmature and aiBone->mNode generically * The point of these is it saves you later having to calculate these elements * This is useful when handling rest information or skin information - * If you have multiple armatures on your models we strongly recommend enabling this - * Instead of writing your own multi-root, multi-armature lookups we have done the + * If you have multiple armatures on your models we strongly recommend enabling this + * Instead of writing your own multi-root, multi-armature lookups we have done the * hard work for you :) */ aiProcess_PopulateArmatureData = 0x4000, @@ -579,7 +579,7 @@ enum aiPostProcessSteps * of the imported model. And if so, it uses that. */ aiProcess_EmbedTextures = 0x10000000, - + // aiProcess_GenEntityMeshes = 0x100000, // aiProcess_OptimizeAnimations = 0x200000 // aiProcess_FixTexturePaths = 0x200000 diff --git a/include/assimp/quaternion.h b/include/assimp/quaternion.h index 7c096fe00..6941fbbc3 100644 --- a/include/assimp/quaternion.h +++ b/include/assimp/quaternion.h @@ -73,7 +73,7 @@ public: explicit aiQuaterniont( const aiMatrix3x3t& pRotMatrix); /** Construct from euler angles */ - aiQuaterniont( TReal rotx, TReal roty, TReal rotz); + aiQuaterniont( TReal roty, TReal rotz, TReal rotx); /** Construct from an axis-angle pair */ aiQuaterniont( aiVector3t axis, TReal angle); diff --git a/include/assimp/scene.h b/include/assimp/scene.h index 336eb1ebd..dcf803572 100644 --- a/include/assimp/scene.h +++ b/include/assimp/scene.h @@ -70,7 +70,7 @@ extern "C" { #endif // ------------------------------------------------------------------------------- -/** +/** * A node in the imported hierarchy. * * Each node has name, a parent node (except for the root node), @@ -149,12 +149,12 @@ struct ASSIMP_API aiNode * @param name Name to search for * @return nullptr or a valid Node if the search was successful. */ - inline + inline const aiNode* FindNode(const aiString& name) const { return FindNode(name.data); } - inline + inline aiNode* FindNode(const aiString& name) { return FindNode(name.data); } @@ -353,34 +353,34 @@ struct aiScene //! Check whether the scene contains meshes //! Unless no special scene flags are set this will always be true. - inline bool HasMeshes() const { - return mMeshes != nullptr && mNumMeshes > 0; + inline bool HasMeshes() const { + return mMeshes != nullptr && mNumMeshes > 0; } //! Check whether the scene contains materials //! Unless no special scene flags are set this will always be true. - inline bool HasMaterials() const { - return mMaterials != nullptr && mNumMaterials > 0; + inline bool HasMaterials() const { + return mMaterials != nullptr && mNumMaterials > 0; } //! Check whether the scene contains lights - inline bool HasLights() const { - return mLights != nullptr && mNumLights > 0; + inline bool HasLights() const { + return mLights != nullptr && mNumLights > 0; } //! Check whether the scene contains textures inline bool HasTextures() const { - return mTextures != nullptr && mNumTextures > 0; + return mTextures != nullptr && mNumTextures > 0; } //! Check whether the scene contains cameras inline bool HasCameras() const { - return mCameras != nullptr && mNumCameras > 0; + return mCameras != nullptr && mNumCameras > 0; } //! Check whether the scene contains animations - inline bool HasAnimations() const { - return mAnimations != nullptr && mNumAnimations > 0; + inline bool HasAnimations() const { + return mAnimations != nullptr && mNumAnimations > 0; } //! Returns a short filename from a full path @@ -395,22 +395,35 @@ struct aiScene //! Returns an embedded texture const aiTexture* GetEmbeddedTexture(const char* filename) const { + return GetEmbeddedTextureAndIndex(filename).first; + } + + //! Returns an embedded texture and its index + std::pair GetEmbeddedTextureAndIndex(const char* filename) const { + if (nullptr==filename) { + return std::make_pair(nullptr, -1); + } // lookup using texture ID (if referenced like: "*1", "*2", etc.) if ('*' == *filename) { int index = std::atoi(filename + 1); - if (0 > index || mNumTextures <= static_cast(index)) - return nullptr; - return mTextures[index]; + if (0 > index || mNumTextures <= static_cast(index)) { + return std::make_pair(nullptr, -1); + } + return std::make_pair(mTextures[index], index); } // lookup using filename const char* shortFilename = GetShortFilename(filename); + if (nullptr == shortFilename) { + return std::make_pair(nullptr, -1); + } + for (unsigned int i = 0; i < mNumTextures; i++) { const char* shortTextureFilename = GetShortFilename(mTextures[i]->mFilename.C_Str()); if (strcmp(shortTextureFilename, shortFilename) == 0) { - return mTextures[i]; + return std::make_pair(mTextures[i], i); } } - return nullptr; + return std::make_pair(nullptr, -1); } #endif // __cplusplus diff --git a/include/assimp/vector2.inl b/include/assimp/vector2.inl index 0ca440e72..b51dd0ec2 100644 --- a/include/assimp/vector2.inl +++ b/include/assimp/vector2.inl @@ -190,7 +190,7 @@ aiVector2t operator + (const aiVector2t& v1, const aiVector2t -inline +inline aiVector2t operator - (const aiVector2t& v1, const aiVector2t& v2) { return aiVector2t( v1.x - v2.x, v1.y - v2.y); } @@ -198,7 +198,7 @@ aiVector2t operator - (const aiVector2t& v1, const aiVector2t -inline +inline TReal operator * (const aiVector2t& v1, const aiVector2t& v2) { return v1.x*v2.x + v1.y*v2.y; } @@ -206,7 +206,7 @@ TReal operator * (const aiVector2t& v1, const aiVector2t& v2) { // ------------------------------------------------------------------------------------------------ // scalar multiplication template -inline +inline aiVector2t operator * ( TReal f, const aiVector2t& v) { return aiVector2t( f*v.x, f*v.y); } @@ -214,7 +214,7 @@ aiVector2t operator * ( TReal f, const aiVector2t& v) { // ------------------------------------------------------------------------------------------------ // and the other way around template -inline +inline aiVector2t operator * ( const aiVector2t& v, TReal f) { return aiVector2t( f*v.x, f*v.y); } @@ -222,7 +222,7 @@ aiVector2t operator * ( const aiVector2t& v, TReal f) { // ------------------------------------------------------------------------------------------------ // scalar division template -inline +inline aiVector2t operator / ( const aiVector2t& v, TReal f) { return v * (1/f); } @@ -230,7 +230,7 @@ aiVector2t operator / ( const aiVector2t& v, TReal f) { // ------------------------------------------------------------------------------------------------ // vector division template -inline +inline aiVector2t operator / ( const aiVector2t& v, const aiVector2t& v2) { return aiVector2t(v.x / v2.x,v.y / v2.y); } @@ -238,7 +238,7 @@ aiVector2t operator / ( const aiVector2t& v, const aiVector2t -inline +inline aiVector2t operator - ( const aiVector2t& v) { return aiVector2t( -v.x, -v.y); } diff --git a/include/assimp/vector3.h b/include/assimp/vector3.h index b084cf9c3..e3ad0b680 100644 --- a/include/assimp/vector3.h +++ b/include/assimp/vector3.h @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2021, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -65,27 +63,49 @@ template class aiMatrix3x3t; template class aiMatrix4x4t; // --------------------------------------------------------------------------- -/** Represents a three-dimensional vector. */ +/// @brief Represents a three-dimensional vector. +// --------------------------------------------------------------------------- template class aiVector3t { public: + /// @brief The default class constructor. aiVector3t() AI_NO_EXCEPT : x(), y(), z() {} + + /// @brief The class constructor with the components. + /// @param _x The x-component for the vector. + /// @param _y The y-component for the vector. + /// @param _z The z-component for the vector. aiVector3t(TReal _x, TReal _y, TReal _z) : x(_x), y(_y), z(_z) {} + + /// @brief The class constructor with a default value. + /// @param _xyz The value for x, y and z. explicit aiVector3t (TReal _xyz ) : x(_xyz), y(_xyz), z(_xyz) {} + + /// @brief The copy constructor. + /// @param o The instance to copy from. aiVector3t( const aiVector3t& o ) = default; - // combined operators + /// @brief combined operators + /// @brief The copy constructor. const aiVector3t& operator += (const aiVector3t& o); + + /// @brief The copy constructor. const aiVector3t& operator -= (const aiVector3t& o); + + /// @brief The copy constructor. const aiVector3t& operator *= (TReal f); + + /// @brief The copy constructor. const aiVector3t& operator /= (TReal f); - // transform vector by matrix + /// @brief Transform vector by matrix aiVector3t& operator *= (const aiMatrix3x3t& mat); aiVector3t& operator *= (const aiMatrix4x4t& mat); - // access a single element + /// @brief access a single element, const. TReal operator[](unsigned int i) const; + + /// @brief access a single element, non-const. TReal& operator[](unsigned int i); // comparison @@ -93,6 +113,7 @@ public: bool operator!= (const aiVector3t& other) const; bool operator < (const aiVector3t& other) const; + /// @brief bool Equal(const aiVector3t& other, TReal epsilon = 1e-6) const; template diff --git a/packaging/windows-innosetup/readme_installer.txt b/packaging/windows-innosetup/readme_installer.txt index 6ea969dc1..252c396af 100644 --- a/packaging/windows-innosetup/readme_installer.txt +++ b/packaging/windows-innosetup/readme_installer.txt @@ -10,7 +10,7 @@ http://assimp.sf.net Troubleshooting =============== -1. Missing d3dx9_(some-number).dll? +1. Missing d3dx9_(some-number).dll? Install the latest DirectX runtime or grab the file from somewhere (that's evil but mostly fine). 2. Application configuration not correct / missing msvcr***.dll? diff --git a/packaging/windows-innosetup/readme_installer_vieweronly.txt b/packaging/windows-innosetup/readme_installer_vieweronly.txt index 1e84c577d..0acd6ef52 100644 --- a/packaging/windows-innosetup/readme_installer_vieweronly.txt +++ b/packaging/windows-innosetup/readme_installer_vieweronly.txt @@ -19,7 +19,7 @@ Viewer Troubleshooting =============== -1. Missing d3dx9_(number).dll? +1. Missing d3dx9_(number).dll? Install the latest DirectX runtime or grab the file from somewhere (that's evil but mostly fine). 2. Application configuration not correct / missing msvcr***.dll? diff --git a/packaging/windows-mkzip/bin_readme.txt b/packaging/windows-mkzip/bin_readme.txt index 5cff1f30e..f4402d6bf 100644 --- a/packaging/windows-mkzip/bin_readme.txt +++ b/packaging/windows-mkzip/bin_readme.txt @@ -19,7 +19,7 @@ Viewer Troubleshooting =============== -1. Missing d3dx9_42.dll? +1. Missing d3dx9_42.dll? Install the latest DirectX runtime or grab the file from somewhere (that's evil but mostly fine). 2. Application configuration not correct / missing msv*** DLLs? diff --git a/port/AndroidJNI/CMakeLists.txt b/port/AndroidJNI/CMakeLists.txt index 43e842848..8c821c72a 100644 --- a/port/AndroidJNI/CMakeLists.txt +++ b/port/AndroidJNI/CMakeLists.txt @@ -3,10 +3,10 @@ cmake_minimum_required(VERSION 3.10) include_directories(./) include_directories(./../../) add_library( # Defines the name of the library. - android_jniiosystem + android_jniiosystem # Implements a static library. - STATIC + STATIC # relative path to source file(s). AndroidJNIIOSystem.cpp diff --git a/port/AssimpDelphi/Readme.txt b/port/AssimpDelphi/Readme.txt index 07d6935ae..1ec6d2109 100644 --- a/port/AssimpDelphi/Readme.txt +++ b/port/AssimpDelphi/Readme.txt @@ -1,6 +1,6 @@ This is a set of Delphi units for using the Assimp C DLL. This was created for use with Delphi 7, but should be usable as-is or with minimal modifications with later Delphi versions. -This set of headers is enough to load and display a model with external textures. Since I'm not familiar with animated models and some of the other functionality of the assimp library, I did not convert the headers for those features. +This set of headers is enough to load and display a model with external textures. Since I'm not familiar with animated models and some of the other functionality of the assimp library, I did not convert the headers for those features. See http://sourceforge.net/tracker/?func=detail&aid=3212646&group_id=226462&atid=1067634 for the original patch diff --git a/port/PyAssimp/pyassimp/helper.py b/port/PyAssimp/pyassimp/helper.py index 7a4b2bdcb..7c14f6097 100644 --- a/port/PyAssimp/pyassimp/helper.py +++ b/port/PyAssimp/pyassimp/helper.py @@ -34,7 +34,8 @@ if os.name=='posix': additional_dirs.extend([item for item in os.environ['LD_LIBRARY_PATH'].split(':') if item]) # check if running from anaconda. - if "conda" or "continuum" in sys.version.lower(): + anaconda_keywords = ("conda", "continuum") + if any(k in sys.version.lower() for k in anaconda_keywords): cur_path = get_python_lib() pattern = re.compile('.*\/lib\/') conda_lib = pattern.match(cur_path).group() diff --git a/port/jassimp/jassimp-native/src/jassimp.cpp b/port/jassimp/jassimp-native/src/jassimp.cpp index 0cf01b1e3..6661ce9c4 100644 --- a/port/jassimp/jassimp-native/src/jassimp.cpp +++ b/port/jassimp/jassimp-native/src/jassimp.cpp @@ -15,7 +15,7 @@ #define lprintf(...) printf (__VA_ARGS__) #endif /* ANDROID */ #else -#define lprintf +#define lprintf #endif static std::string gLastErrorString; @@ -63,7 +63,7 @@ static bool createInstance(JNIEnv *env, const char* className, jobject& newInsta newInstance = env->NewObject(clazz, ctr_id); - if (NULL == newInstance) + if (NULL == newInstance) { lprintf("error calling no-arg constructor for class %s\n", className); return false; @@ -94,7 +94,7 @@ static bool createInstance(JNIEnv *env, const char* className, const char* signa newInstance = env->NewObjectA(clazz, ctr_id, params); - if (NULL == newInstance) + if (NULL == newInstance) { lprintf("error calling constructor for class %s, signature %s\n", className, signature); return false; @@ -229,7 +229,7 @@ static bool getStaticField(JNIEnv *env, const char* className, const char* field } -static bool call(JNIEnv *env, jobject object, const char* typeName, const char* methodName, +static bool call(JNIEnv *env, jobject object, const char* typeName, const char* methodName, const char* signature,/* const*/ jvalue* params) { jclass clazz = env->FindClass(typeName); @@ -275,7 +275,7 @@ 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, +static jobject callo(JNIEnv *env, jobject object, const char* typeName, const char* methodName, const char* signature,/* const*/ jvalue* params) { jclass clazz = env->FindClass(typeName); @@ -300,7 +300,7 @@ static jobject callo(JNIEnv *env, jobject object, const char* typeName, const ch return jReturnValue; } -static int calli(JNIEnv *env, jobject object, const char* typeName, const char* methodName, +static int calli(JNIEnv *env, jobject object, const char* typeName, const char* methodName, const char* signature) { jclass clazz = env->FindClass(typeName); @@ -325,7 +325,7 @@ static int calli(JNIEnv *env, jobject object, const char* typeName, const char* return (int) jReturnValue; } -static int callc(JNIEnv *env, jobject object, const char* typeName, const char* methodName, +static int callc(JNIEnv *env, jobject object, const char* typeName, const char* methodName, const char* signature) { jclass clazz = env->FindClass(typeName); @@ -351,7 +351,7 @@ static int callc(JNIEnv *env, jobject object, const char* typeName, const char* } -static bool callStaticObject(JNIEnv *env, const char* typeName, const char* methodName, +static bool callStaticObject(JNIEnv *env, const char* typeName, const char* methodName, const char* signature,/* const*/ jvalue* params, jobject& returnValue) { jclass clazz = env->FindClass(typeName); @@ -441,13 +441,13 @@ static bool copyBufferArray(JNIEnv *env, jobject jMesh, const char* jBufferName, class JavaIOStream : public Assimp::IOStream { -private: +private: size_t pos; size_t size; char* buffer; jobject jIOStream; - + public: JavaIOStream(size_t size, char* buffer, jobject jIOStream) : pos(0), @@ -455,28 +455,28 @@ public: buffer(buffer), jIOStream(jIOStream) {}; - - - ~JavaIOStream(void) + + + ~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) + size_t Write(const void* pvBuffer, size_t pSize, size_t pCount) { return 0; }; - + aiReturn Seek(size_t pOffset, aiOrigin pOrigin) { if (aiOrigin_SET == pOrigin) { @@ -499,40 +499,40 @@ public: } 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]; @@ -544,27 +544,27 @@ class JavaIOSystem : public Assimp::IOSystem { { 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)) @@ -581,28 +581,28 @@ class JavaIOSystem : public Assimp::IOSystem { }; 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; }; - - + + }; class JavaProgressHandler : public Assimp::ProgressHandler { private: JNIEnv* mJniEnv; jobject& mJavaProgressHandler; - + public: JavaProgressHandler(JNIEnv* env, jobject& javaProgressHandler) : mJniEnv(env), mJavaProgressHandler(javaProgressHandler) {}; - + bool Update(float percentage) { jvalue params[1]; @@ -623,12 +623,12 @@ static bool loadMeshes(JNIEnv *env, const aiScene* cScene, jobject& jScene) jobject jMesh = NULL; SmartLocalRef refMesh(env, jMesh); - if (!createInstance(env, "jassimp/AiMesh", jMesh)) + if (!createInstance(env, "jassimp/AiMesh", jMesh)) { return false; } - + /* add mesh to m_meshes java.util.List */ jobject jMeshes = NULL; SmartLocalRef refMeshes(env, jMeshes); @@ -671,7 +671,7 @@ static bool loadMeshes(JNIEnv *env, const aiScene* cScene, jobject& jScene) /* determine face buffer size */ bool isPureTriangle = cMesh->mPrimitiveTypes == aiPrimitiveType_TRIANGLE; size_t faceBufferSize; - if (isPureTriangle) + if (isPureTriangle) { faceBufferSize = cMesh->mNumFaces * 3 * sizeof(unsigned int); } @@ -715,7 +715,7 @@ static bool loadMeshes(JNIEnv *env, const aiScene* cScene, jobject& jScene) /* push face data to java */ if (cMesh->mNumFaces > 0) { - if (isPureTriangle) + if (isPureTriangle) { char* faceBuffer = (char*) malloc(faceBufferSize); @@ -729,7 +729,7 @@ static bool loadMeshes(JNIEnv *env, const aiScene* cScene, jobject& jScene) free(faceBuffer); - if (!res) + if (!res) { lprintf("could not copy face data\n"); return false; @@ -750,7 +750,7 @@ static bool loadMeshes(JNIEnv *env, const aiScene* cScene, jobject& jScene) memcpy(faceBuffer + faceBufferPos, cMesh->mFaces[face].mIndices, faceDataSize); faceBufferPos += faceDataSize; } - + if (faceBufferPos != faceBufferSize) { /* this should really not happen */ @@ -766,7 +766,7 @@ static bool loadMeshes(JNIEnv *env, const aiScene* cScene, jobject& jScene) free(faceBuffer); free(offsetBuffer); - if (!res) + if (!res) { lprintf("could not copy face data\n"); return false; @@ -933,7 +933,7 @@ static bool loadMeshes(JNIEnv *env, const aiScene* cScene, jobject& jScene) jobject jBone; SmartLocalRef refBone(env, jBone); - if (!createInstance(env, "jassimp/AiBone", jBone)) + if (!createInstance(env, "jassimp/AiBone", jBone)) { return false; } @@ -988,7 +988,7 @@ static bool loadMeshes(JNIEnv *env, const aiScene* cScene, jobject& jScene) wrapParams[0].l = jMatrixArr; jobject jMatrix; SmartLocalRef refMatrix(env, jMatrix); - + if (!callStaticObject(env, "jassimp/Jassimp", "wrapMatrix", "([F)Ljava/lang/Object;", wrapParams, jMatrix)) { return false; @@ -1012,7 +1012,7 @@ static bool loadMeshes(JNIEnv *env, const aiScene* cScene, jobject& jScene) { return false; } - + if (!setFloatField(env, jBoneWeight, "m_weight", cBone->mWeights[w].mWeight)) { return false; @@ -1228,7 +1228,7 @@ static bool loadSceneNode(JNIEnv *env, const aiNode *cNode, jobject parent, jobj wrapNodeParams[3].l = jNodeName; jobject jNode; if (!callStaticObject(env, "jassimp/Jassimp", "wrapSceneNode", - "(Ljava/lang/Object;Ljava/lang/Object;[ILjava/lang/String;)Ljava/lang/Object;", wrapNodeParams, jNode)) + "(Ljava/lang/Object;Ljava/lang/Object;[ILjava/lang/String;)Ljava/lang/Object;", wrapNodeParams, jNode)) { return false; } @@ -1287,7 +1287,7 @@ static bool loadSceneGraph(JNIEnv *env, const aiScene* cScene, jobject& jScene) } -static bool loadMaterials(JNIEnv *env, const aiScene* cScene, jobject& jScene) +static bool loadMaterials(JNIEnv *env, const aiScene* cScene, jobject& jScene) { for (unsigned int m = 0; m < cScene->mNumMaterials; m++) { @@ -1320,7 +1320,7 @@ static bool loadMaterials(JNIEnv *env, const aiScene* cScene, jobject& jScene) } /* set texture numbers */ - for (int ttInd = aiTextureType_DIFFUSE; ttInd < aiTextureType_UNKNOWN; ttInd++) + for (int ttInd = aiTextureType_DIFFUSE; ttInd < aiTextureType_UNKNOWN; ttInd++) { aiTextureType tt = static_cast(ttInd); @@ -1341,7 +1341,7 @@ static bool loadMaterials(JNIEnv *env, const aiScene* cScene, jobject& jScene) for (unsigned int p = 0; p < cMaterial->mNumProperties; p++) { - //printf("%s - %u - %u\n", cScene->mMaterials[m]->mProperties[p]->mKey.C_Str(), + //printf("%s - %u - %u\n", cScene->mMaterials[m]->mProperties[p]->mKey.C_Str(), // cScene->mMaterials[m]->mProperties[p]->mSemantic, // cScene->mMaterials[m]->mProperties[p]->mDataLength); @@ -1362,9 +1362,9 @@ static bool loadMaterials(JNIEnv *env, const aiScene* cScene, jobject& jScene) /* special case conversion for color3 */ - if (NULL != strstr(cProperty->mKey.C_Str(), "clr") && + if (NULL != strstr(cProperty->mKey.C_Str(), "clr") && cProperty->mType == aiPTI_Float && - cProperty->mDataLength == 3 * sizeof(float)) + cProperty->mDataLength == 3 * sizeof(float)) { jobject jData = NULL; SmartLocalRef refData(env, jData); @@ -1380,16 +1380,16 @@ static bool loadMaterials(JNIEnv *env, const aiScene* cScene, jobject& jScene) } constructorParams[4].l = jData; - if (!createInstance(env, "jassimp/AiMaterial$Property", "(Ljava/lang/String;IIILjava/lang/Object;)V", + if (!createInstance(env, "jassimp/AiMaterial$Property", "(Ljava/lang/String;IIILjava/lang/Object;)V", constructorParams, jProperty)) { return false; } } /* special case conversion for color4 */ - else if (NULL != strstr(cProperty->mKey.C_Str(), "clr") && + else if (NULL != strstr(cProperty->mKey.C_Str(), "clr") && cProperty->mType == aiPTI_Float && - cProperty->mDataLength == 4 * sizeof(float)) + cProperty->mDataLength == 4 * sizeof(float)) { jobject jData = NULL; SmartLocalRef refData(env, jData); @@ -1406,13 +1406,13 @@ static bool loadMaterials(JNIEnv *env, const aiScene* cScene, jobject& jScene) } constructorParams[4].l = jData; - if (!createInstance(env, "jassimp/AiMaterial$Property", "(Ljava/lang/String;IIILjava/lang/Object;)V", + if (!createInstance(env, "jassimp/AiMaterial$Property", "(Ljava/lang/String;IIILjava/lang/Object;)V", constructorParams, jProperty)) { return false; } } - else if (cProperty->mType == aiPTI_Float && cProperty->mDataLength == sizeof(float)) + else if (cProperty->mType == aiPTI_Float && cProperty->mDataLength == sizeof(float)) { jobject jData = NULL; SmartLocalRef refData(env, jData); @@ -1425,13 +1425,13 @@ static bool loadMaterials(JNIEnv *env, const aiScene* cScene, jobject& jScene) } constructorParams[4].l = jData; - if (!createInstance(env, "jassimp/AiMaterial$Property", "(Ljava/lang/String;IIILjava/lang/Object;)V", + if (!createInstance(env, "jassimp/AiMaterial$Property", "(Ljava/lang/String;IIILjava/lang/Object;)V", constructorParams, jProperty)) { return false; } } - else if (cProperty->mType == aiPTI_Integer && cProperty->mDataLength == sizeof(int)) + else if (cProperty->mType == aiPTI_Integer && cProperty->mDataLength == sizeof(int)) { jobject jData = NULL; SmartLocalRef refData(env, jData); @@ -1444,26 +1444,26 @@ static bool loadMaterials(JNIEnv *env, const aiScene* cScene, jobject& jScene) } constructorParams[4].l = jData; - if (!createInstance(env, "jassimp/AiMaterial$Property", "(Ljava/lang/String;IIILjava/lang/Object;)V", + if (!createInstance(env, "jassimp/AiMaterial$Property", "(Ljava/lang/String;IIILjava/lang/Object;)V", constructorParams, jProperty)) { return false; } } - else if (cProperty->mType == aiPTI_String) + else if (cProperty->mType == aiPTI_String) { /* skip length prefix */ jobject jData = env->NewStringUTF(cProperty->mData + 4); SmartLocalRef refData(env, jData); constructorParams[4].l = jData; - if (!createInstance(env, "jassimp/AiMaterial$Property", "(Ljava/lang/String;IIILjava/lang/Object;)V", + if (!createInstance(env, "jassimp/AiMaterial$Property", "(Ljava/lang/String;IIILjava/lang/Object;)V", constructorParams, jProperty)) { return false; } } - else + else { constructorParams[4].i = cProperty->mDataLength; @@ -1521,7 +1521,7 @@ static bool loadMaterials(JNIEnv *env, const aiScene* cScene, jobject& jScene) } -static bool loadAnimations(JNIEnv *env, const aiScene* cScene, jobject& jScene) +static bool loadAnimations(JNIEnv *env, const aiScene* cScene, jobject& jScene) { lprintf("converting %d animations ...\n", cScene->mNumAnimations); @@ -1603,19 +1603,19 @@ static bool loadAnimations(JNIEnv *env, const aiScene* cScene, jobject& jScene) } /* copy keys */ - if (!copyBuffer(env, jNodeAnim, "m_posKeys", cNodeAnim->mPositionKeys, + if (!copyBuffer(env, jNodeAnim, "m_posKeys", cNodeAnim->mPositionKeys, cNodeAnim->mNumPositionKeys * sizeof(aiVectorKey))) { return false; } - if (!copyBuffer(env, jNodeAnim, "m_rotKeys", cNodeAnim->mRotationKeys, + if (!copyBuffer(env, jNodeAnim, "m_rotKeys", cNodeAnim->mRotationKeys, cNodeAnim->mNumRotationKeys * sizeof(aiQuatKey))) { return false; } - if (!copyBuffer(env, jNodeAnim, "m_scaleKeys", cNodeAnim->mScalingKeys, + if (!copyBuffer(env, jNodeAnim, "m_scaleKeys", cNodeAnim->mScalingKeys, cNodeAnim->mNumScalingKeys * sizeof(aiVectorKey))) { return false; @@ -1629,7 +1629,7 @@ static bool loadAnimations(JNIEnv *env, const aiScene* cScene, jobject& jScene) } -static bool loadLights(JNIEnv *env, const aiScene* cScene, jobject& jScene) +static bool loadLights(JNIEnv *env, const aiScene* cScene, jobject& jScene) { lprintf("converting %d lights ...\n", cScene->mNumLights); @@ -1712,7 +1712,7 @@ static bool loadLights(JNIEnv *env, const aiScene* cScene, jobject& jScene) params[9].l = jAmbient; params[10].f = cLight->mAngleInnerCone; params[11].f = cLight->mAngleOuterCone; - + if (!createInstance(env, "jassimp/AiLight", "(Ljava/lang/String;ILjava/lang/Object;Ljava/lang/Object;FFFLjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;FF)V", params, jLight)) { @@ -1736,13 +1736,13 @@ static bool loadLights(JNIEnv *env, const aiScene* cScene, jobject& jScene) } } - lprintf("converting lights finished ...\n"); + lprintf("converting lights finished ...\n"); return true; } -static bool loadCameras(JNIEnv *env, const aiScene* cScene, jobject& jScene) +static bool loadCameras(JNIEnv *env, const aiScene* cScene, jobject& jScene) { lprintf("converting %d cameras ...\n", cScene->mNumCameras); @@ -1799,7 +1799,7 @@ static bool loadCameras(JNIEnv *env, const aiScene* cScene, jobject& jScene) params[5].f = cCamera->mClipPlaneNear; params[6].f = cCamera->mClipPlaneFar; params[7].f = cCamera->mAspect; - + if (!createInstance(env, "jassimp/AiCamera", "(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;FFFF)V", params, jCamera)) { @@ -1901,25 +1901,25 @@ JNIEXPORT jstring JNICALL Java_jassimp_Jassimp_getErrorString JNIEXPORT jobject JNICALL Java_jassimp_Jassimp_aiImportFile (JNIEnv *env, jclass jClazz, jstring jFilename, jlong postProcess, jobject ioSystem, jobject progressHandler) { - jobject jScene = NULL; + jobject jScene = NULL; /* convert params */ const char* cFilename = env->GetStringUTFChars(jFilename, NULL); - + Assimp::Importer imp; - + if(ioSystem != NULL) { - imp.SetIOHandler(new JavaIOSystem(env, ioSystem)); + imp.SetIOHandler(new JavaIOSystem(env, ioSystem)); lprintf("Created aiFileIO\n"); } - + if(progressHandler != NULL) { imp.SetProgressHandler(new JavaProgressHandler(env, progressHandler)); } - + lprintf("opening file: %s\n", cFilename); /* do import */ @@ -1931,7 +1931,7 @@ JNIEXPORT jobject JNICALL Java_jassimp_Jassimp_aiImportFile goto error; } - if (!createInstance(env, "jassimp/AiScene", jScene)) + if (!createInstance(env, "jassimp/AiScene", jScene)) { goto error; } diff --git a/samples/SimpleAssimpViewX/ModelLoaderHelperClasses.h b/samples/SimpleAssimpViewX/ModelLoaderHelperClasses.h index 0e6dfab3c..1803ad122 100644 --- a/samples/SimpleAssimpViewX/ModelLoaderHelperClasses.h +++ b/samples/SimpleAssimpViewX/ModelLoaderHelperClasses.h @@ -15,7 +15,7 @@ /* workflow: - 1) create a new scene wrapper + 1) create a new scene wrapper 2) populate an array of of meshHelpers for each mesh in the original scene 3) (eventually) create an animator instance 4) scale the asset (needed?) @@ -24,16 +24,16 @@ 5b) create a static vertex buffer 5c) create index buffer 5d) populate the index buffer - 5e) (eventually) gather weights + 5e) (eventually) gather weights */ #define BUFFER_OFFSET(i) ((char *)NULL + (i)) -struct Vertex +struct Vertex { aiVector3D vPosition; aiVector3D vNormal; - + aiColor4D dColorDiffuse; aiVector3D vTangent; aiVector3D vBitangent; @@ -46,33 +46,33 @@ struct Vertex // Helper Class to store GPU related resources from a given aiMesh // Modeled after AssimpView asset helper -@interface MeshHelper : NSObject -{ +@interface MeshHelper : NSObject +{ // Display list ID, this one shots *all drawing* of the mesh. Only ever use this to draw. Booya. GLuint displayList; - + // VAO that encapsulates all VBO drawing state GLuint vao; - + // VBOs GLuint vertexBuffer; GLuint indexBuffer; GLuint normalBuffer; GLuint numIndices; - + // texture GLuint textureID; - - // Material + + // Material aiColor4D diffuseColor; aiColor4D specularColor; aiColor4D ambientColor; aiColor4D emissiveColor; - + GLfloat opacity; GLfloat shininess; GLfloat specularStrength; - + BOOL twoSided; } diff --git a/samples/SimpleAssimpViewX/MyDocument.h b/samples/SimpleAssimpViewX/MyDocument.h index 16745dc3c..67675e306 100644 --- a/samples/SimpleAssimpViewX/MyDocument.h +++ b/samples/SimpleAssimpViewX/MyDocument.h @@ -20,27 +20,27 @@ #import -@interface MyDocument : NSPersistentDocument +@interface MyDocument : NSPersistentDocument { CVDisplayLinkRef _displayLink; NSOpenGLContext* _glContext; NSOpenGLPixelFormat* _glPixelFormat; - + NSView* _view; - + // Assimp Stuff aiScene* _scene; aiVector3D scene_min, scene_max, scene_center; - double normalizedScale; - + double normalizedScale; + // Our array of textures. GLuint *textureIds; - + // only used if we use - NSMutableArray* modelMeshes; + NSMutableArray* modelMeshes; BOOL builtBuffers; - - NSMutableDictionary* textureDictionary; // Array of Dicionaries that map image filenames to textureIds + + NSMutableDictionary* textureDictionary; // Array of Dicionaries that map image filenames to textureIds } @property (retain) IBOutlet NSView* _view; diff --git a/samples/SimpleOpenGL/Sample_SimpleOpenGL.c b/samples/SimpleOpenGL/Sample_SimpleOpenGL.c index bcb109564..7aa306ed4 100644 --- a/samples/SimpleOpenGL/Sample_SimpleOpenGL.c +++ b/samples/SimpleOpenGL/Sample_SimpleOpenGL.c @@ -29,7 +29,7 @@ /* ---------------------------------------------------------------------------- */ inline static void print_run_command(const char* command_name) { - printf("Run '%s %s' for more information.\n", + printf("Run '%s %s' for more information.\n", PROJECT_NAME, command_name); } @@ -43,7 +43,7 @@ inline static void print_error(const char* msg) { /* ---------------------------------------------------------------------------- */ inline static void print_usage() { - static const char* usage_format = + static const char* usage_format = "Usage: " PROJECT_NAME " " DOUBLE_NEW_LINE diff --git a/samples/SimpleTexturedDirectx11/CMakeLists.txt b/samples/SimpleTexturedDirectx11/CMakeLists.txt index 82144caa9..007ada3af 100644 --- a/samples/SimpleTexturedDirectx11/CMakeLists.txt +++ b/samples/SimpleTexturedDirectx11/CMakeLists.txt @@ -24,13 +24,13 @@ LINK_DIRECTORIES( ) ADD_EXECUTABLE( assimp_simpletextureddirectx11 WIN32 - SimpleTexturedDirectx11/Mesh.h + SimpleTexturedDirectx11/Mesh.h SimpleTexturedDirectx11/ModelLoader.cpp SimpleTexturedDirectx11/ModelLoader.h #SimpleTexturedDirectx11/PixelShader.hlsl SimpleTexturedDirectx11/TextureLoader.cpp - SimpleTexturedDirectx11/TextureLoader.h - #SimpleTexturedDirectx11/VertexShader.hlsl + SimpleTexturedDirectx11/TextureLoader.h + #SimpleTexturedDirectx11/VertexShader.hlsl SimpleTexturedDirectx11/main.cpp SimpleTexturedDirectx11/SafeRelease.hpp ${SAMPLES_SHARED_CODE_DIR}/UTFConverter.cpp diff --git a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/ModelLoader.cpp b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/ModelLoader.cpp index 92760d691..18bb10f1e 100644 --- a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/ModelLoader.cpp +++ b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/ModelLoader.cpp @@ -1,6 +1,6 @@ #include "ModelLoader.h" -ModelLoader::ModelLoader() : +ModelLoader::ModelLoader() : dev_(nullptr), devcon_(nullptr), meshes_(), diff --git a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/TextureLoader.cpp b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/TextureLoader.cpp index 01ba343e8..a02c53ca6 100644 --- a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/TextureLoader.cpp +++ b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/TextureLoader.cpp @@ -140,16 +140,16 @@ static WICConvert g_WICConvert[] = { GUID_WICPixelFormatBlackWhite, GUID_WICPixelFormat8bppGray }, // DXGI_FORMAT_R8_UNORM - { GUID_WICPixelFormat1bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM - { GUID_WICPixelFormat2bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM - { GUID_WICPixelFormat4bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM - { GUID_WICPixelFormat8bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM + { GUID_WICPixelFormat1bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM + { GUID_WICPixelFormat2bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM + { GUID_WICPixelFormat4bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM + { GUID_WICPixelFormat8bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM - { GUID_WICPixelFormat2bppGray, GUID_WICPixelFormat8bppGray }, // DXGI_FORMAT_R8_UNORM - { GUID_WICPixelFormat4bppGray, GUID_WICPixelFormat8bppGray }, // DXGI_FORMAT_R8_UNORM + { GUID_WICPixelFormat2bppGray, GUID_WICPixelFormat8bppGray }, // DXGI_FORMAT_R8_UNORM + { GUID_WICPixelFormat4bppGray, GUID_WICPixelFormat8bppGray }, // DXGI_FORMAT_R8_UNORM - { GUID_WICPixelFormat16bppGrayFixedPoint, GUID_WICPixelFormat16bppGrayHalf }, // DXGI_FORMAT_R16_FLOAT - { GUID_WICPixelFormat32bppGrayFixedPoint, GUID_WICPixelFormat32bppGrayFloat }, // DXGI_FORMAT_R32_FLOAT + { GUID_WICPixelFormat16bppGrayFixedPoint, GUID_WICPixelFormat16bppGrayHalf }, // DXGI_FORMAT_R16_FLOAT + { GUID_WICPixelFormat32bppGrayFixedPoint, GUID_WICPixelFormat32bppGrayFloat }, // DXGI_FORMAT_R32_FLOAT #ifdef DXGI_1_2_FORMATS @@ -165,10 +165,10 @@ static WICConvert g_WICConvert[] = { GUID_WICPixelFormat32bppBGR101010, GUID_WICPixelFormat32bppRGBA1010102 }, // DXGI_FORMAT_R10G10B10A2_UNORM - { GUID_WICPixelFormat24bppBGR, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM - { GUID_WICPixelFormat24bppRGB, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM - { GUID_WICPixelFormat32bppPBGRA, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM - { GUID_WICPixelFormat32bppPRGBA, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM + { GUID_WICPixelFormat24bppBGR, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM + { GUID_WICPixelFormat24bppRGB, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM + { GUID_WICPixelFormat32bppPBGRA, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM + { GUID_WICPixelFormat32bppPRGBA, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM { GUID_WICPixelFormat48bppRGB, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM { GUID_WICPixelFormat48bppBGR, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM @@ -176,21 +176,21 @@ static WICConvert g_WICConvert[] = { GUID_WICPixelFormat64bppPRGBA, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM { GUID_WICPixelFormat64bppPBGRA, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM - { GUID_WICPixelFormat48bppRGBFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT - { GUID_WICPixelFormat48bppBGRFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT - { GUID_WICPixelFormat64bppRGBAFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT - { GUID_WICPixelFormat64bppBGRAFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT - { GUID_WICPixelFormat64bppRGBFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT - { GUID_WICPixelFormat64bppRGBHalf, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT - { GUID_WICPixelFormat48bppRGBHalf, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT + { GUID_WICPixelFormat48bppRGBFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT + { GUID_WICPixelFormat48bppBGRFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT + { GUID_WICPixelFormat64bppRGBAFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT + { GUID_WICPixelFormat64bppBGRAFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT + { GUID_WICPixelFormat64bppRGBFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT + { GUID_WICPixelFormat64bppRGBHalf, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT + { GUID_WICPixelFormat48bppRGBHalf, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT - { GUID_WICPixelFormat96bppRGBFixedPoint, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT - { GUID_WICPixelFormat128bppPRGBAFloat, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT - { GUID_WICPixelFormat128bppRGBFloat, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT - { GUID_WICPixelFormat128bppRGBAFixedPoint, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT - { GUID_WICPixelFormat128bppRGBFixedPoint, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT + { GUID_WICPixelFormat96bppRGBFixedPoint, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT + { GUID_WICPixelFormat128bppPRGBAFloat, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT + { GUID_WICPixelFormat128bppRGBFloat, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT + { GUID_WICPixelFormat128bppRGBAFixedPoint, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT + { GUID_WICPixelFormat128bppRGBFixedPoint, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT - { GUID_WICPixelFormat32bppCMYK, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM + { GUID_WICPixelFormat32bppCMYK, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM { GUID_WICPixelFormat64bppCMYK, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM { GUID_WICPixelFormat40bppCMYKAlpha, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM { GUID_WICPixelFormat80bppCMYKAlpha, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM @@ -198,7 +198,7 @@ static WICConvert g_WICConvert[] = #if (_WIN32_WINNT >= 0x0602 /*_WIN32_WINNT_WIN8*/) { GUID_WICPixelFormat32bppRGB, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM { GUID_WICPixelFormat64bppRGB, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM - { GUID_WICPixelFormat64bppPRGBAHalf, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT + { GUID_WICPixelFormat64bppPRGBAHalf, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT #endif // We don't support n-channel formats diff --git a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/main.cpp b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/main.cpp index 02e2b6088..3f8d15320 100644 --- a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/main.cpp +++ b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/main.cpp @@ -128,7 +128,7 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, int argc; LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); if (!argv) { - MessageBox(nullptr, + MessageBox(nullptr, TEXT("An error occured while reading command line arguments."), TEXT("Error!"), MB_ICONERROR | MB_OK); @@ -145,8 +145,8 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, // Ensure that a model file has been specified. if (argc < 2) { - MessageBox(nullptr, - TEXT("No model file specified. The program will now close."), + MessageBox(nullptr, + TEXT("No model file specified. The program will now close."), TEXT("Error!"), MB_ICONERROR | MB_OK); free_command_line_allocated_memory(); @@ -157,7 +157,7 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, g_ModelPath = UTFConverter(argv[1]).str(); free_command_line_allocated_memory(); - + WNDCLASSEX wc; MSG msg; @@ -573,7 +573,7 @@ void InitGraphics() HRESULT CompileShaderFromFile(LPCWSTR pFileName, const D3D_SHADER_MACRO* pDefines, LPCSTR pEntryPoint, LPCSTR pShaderModel, ID3DBlob** ppBytecodeBlob) { UINT compileFlags = D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR; - + #ifdef _DEBUG compileFlags |= D3DCOMPILE_DEBUG; #endif diff --git a/samples/SimpleTexturedOpenGL/SimpleTexturedOpenGL/src/model_loading.cpp b/samples/SimpleTexturedOpenGL/SimpleTexturedOpenGL/src/model_loading.cpp index aa2344118..be0e651f1 100644 --- a/samples/SimpleTexturedOpenGL/SimpleTexturedOpenGL/src/model_loading.cpp +++ b/samples/SimpleTexturedOpenGL/SimpleTexturedOpenGL/src/model_loading.cpp @@ -23,7 +23,7 @@ #endif // _MSC_VER #define STB_IMAGE_IMPLEMENTATION -#include "contrib/stb_image/stb_image.h" +#include "contrib/stb/stb_image.h" #ifdef _MSC_VER #pragma warning(default: 4100) // Enable warning 'unreferenced formal parameter' @@ -579,7 +579,7 @@ void KillGLWindow() // Properly Kill The Window if (!DestroyWindow(g_hWnd)) // Are We Able To Destroy The Window MessageBox(nullptr, TEXT("Could Not Release hWnd."), TEXT("SHUTDOWN ERROR"), MB_OK | MB_ICONINFORMATION); g_hWnd = nullptr; - } + } if (g_hInstance) { @@ -727,7 +727,7 @@ BOOL CreateGLWindow(const char* title, int width, int height, int bits, bool ful return FALSE; } - if (nullptr == (hRC=wglCreateContext(hDC))) + if (nullptr == (hRC=wglCreateContext(hDC))) { abortGLInit("Can't Create A GL Rendering Context."); return FALSE; diff --git a/test/models-nonbsd/3D/mar_rifle.source.txt b/test/models-nonbsd/3D/mar_rifle.source.txt index c0cd5fe6d..d7183b7b6 100644 --- a/test/models-nonbsd/3D/mar_rifle.source.txt +++ b/test/models-nonbsd/3D/mar_rifle.source.txt @@ -1,7 +1,7 @@ ===================================================================== From http://telias.free.fr -Model copyright: Elias Tsiantas +Model copyright: Elias Tsiantas ===================================================================== @@ -11,8 +11,8 @@ Notice found on the page: " Free the models is a site that offers free 3d models in 3ds, bryce, poser, lightwave and md2 format. Also a great collection of textures to use in -your favorite modelling and rendering program. All the content is free +your favorite modelling and rendering program. All the content is free for any use. In the future more 3d formats will be added and some other -sections such as wallpapers, 3d screensavers, 3d coding source code and +sections such as wallpapers, 3d screensavers, 3d coding source code and tutorials. " diff --git a/test/models-nonbsd/3DS/cart_wheel.source.txt b/test/models-nonbsd/3DS/cart_wheel.source.txt index 6f43a3a22..bf74fa057 100644 --- a/test/models-nonbsd/3DS/cart_wheel.source.txt +++ b/test/models-nonbsd/3DS/cart_wheel.source.txt @@ -1,7 +1,7 @@ ===================================================================== From http://telias.free.fr -Model copyright: Elias Tsiantas +Model copyright: Elias Tsiantas ===================================================================== @@ -11,8 +11,8 @@ Notice found on the page: " Free the models is a site that offers free 3d models in 3ds, bryce, poser, lightwave and md2 format. Also a great collection of textures to use in -your favorite modelling and rendering program. All the content is free +your favorite modelling and rendering program. All the content is free for any use. In the future more 3d formats will be added and some other -sections such as wallpapers, 3d screensavers, 3d coding source code and +sections such as wallpapers, 3d screensavers, 3d coding source code and tutorials. " diff --git a/test/models-nonbsd/3DS/mar_rifle.source.txt b/test/models-nonbsd/3DS/mar_rifle.source.txt index 6f43a3a22..bf74fa057 100644 --- a/test/models-nonbsd/3DS/mar_rifle.source.txt +++ b/test/models-nonbsd/3DS/mar_rifle.source.txt @@ -1,7 +1,7 @@ ===================================================================== From http://telias.free.fr -Model copyright: Elias Tsiantas +Model copyright: Elias Tsiantas ===================================================================== @@ -11,8 +11,8 @@ Notice found on the page: " Free the models is a site that offers free 3d models in 3ds, bryce, poser, lightwave and md2 format. Also a great collection of textures to use in -your favorite modelling and rendering program. All the content is free +your favorite modelling and rendering program. All the content is free for any use. In the future more 3d formats will be added and some other -sections such as wallpapers, 3d screensavers, 3d coding source code and +sections such as wallpapers, 3d screensavers, 3d coding source code and tutorials. " diff --git a/test/models-nonbsd/3DS/mp5_sil.source.txt b/test/models-nonbsd/3DS/mp5_sil.source.txt index 6f43a3a22..bf74fa057 100644 --- a/test/models-nonbsd/3DS/mp5_sil.source.txt +++ b/test/models-nonbsd/3DS/mp5_sil.source.txt @@ -1,7 +1,7 @@ ===================================================================== From http://telias.free.fr -Model copyright: Elias Tsiantas +Model copyright: Elias Tsiantas ===================================================================== @@ -11,8 +11,8 @@ Notice found on the page: " Free the models is a site that offers free 3d models in 3ds, bryce, poser, lightwave and md2 format. Also a great collection of textures to use in -your favorite modelling and rendering program. All the content is free +your favorite modelling and rendering program. All the content is free for any use. In the future more 3d formats will be added and some other -sections such as wallpapers, 3d screensavers, 3d coding source code and +sections such as wallpapers, 3d screensavers, 3d coding source code and tutorials. " diff --git a/test/models-nonbsd/ASE/Rifle.source.txt b/test/models-nonbsd/ASE/Rifle.source.txt index 1b96f8564..802c57045 100644 --- a/test/models-nonbsd/ASE/Rifle.source.txt +++ b/test/models-nonbsd/ASE/Rifle.source.txt @@ -1,7 +1,7 @@ ===================================================================== From http://telias.free.fr -Model copyright: Elias Tsiantas +Model copyright: Elias Tsiantas ===================================================================== @@ -11,9 +11,9 @@ Notice found on the page: " Free the models is a site that offers free 3d models in 3ds, bryce, poser, lightwave and md2 format. Also a great collection of textures to use in -your favorite modelling and rendering program. All the content is free +your favorite modelling and rendering program. All the content is free for any use. In the future more 3d formats will be added and some other -sections such as wallpapers, 3d screensavers, 3d coding source code and +sections such as wallpapers, 3d screensavers, 3d coding source code and tutorials. " diff --git a/test/models-nonbsd/ASE/Rifle2.source.txt b/test/models-nonbsd/ASE/Rifle2.source.txt index 3fa628db4..7b50d1645 100644 --- a/test/models-nonbsd/ASE/Rifle2.source.txt +++ b/test/models-nonbsd/ASE/Rifle2.source.txt @@ -1,7 +1,7 @@ ===================================================================== From http://telias.free.fr -Model copyright: Elias Tsiantas +Model copyright: Elias Tsiantas ===================================================================== @@ -11,9 +11,9 @@ Notice found on the page: " Free the models is a site that offers free 3d models in 3ds, bryce, poser, lightwave and md2 format. Also a great collection of textures to use in -your favorite modelling and rendering program. All the content is free +your favorite modelling and rendering program. All the content is free for any use. In the future more 3d formats will be added and some other -sections such as wallpapers, 3d screensavers, 3d coding source code and +sections such as wallpapers, 3d screensavers, 3d coding source code and tutorials. " diff --git a/test/models-nonbsd/BLEND/fleurOptonl.source.txt b/test/models-nonbsd/BLEND/fleurOptonl.source.txt index b9c58b5d9..d4c583620 100644 --- a/test/models-nonbsd/BLEND/fleurOptonl.source.txt +++ b/test/models-nonbsd/BLEND/fleurOptonl.source.txt @@ -10,7 +10,7 @@ Puoi utilizzarlo liberamente, modificarlo e migliorarlo. ************* Ma attenzione!! ********************* Nel modificare e utilizzare i modelli, ricorda comunque sempre che : -"il diritto morale all'integrità dell'opera (diritto dell'autore originale) non ti consente di apportare all'opera deformazioni o modificazioni, od ogni altro atto a danno dell'opera stessa, che possano essere di pregiudizio all'onore o alla reputazione dell'autore (la valutazione della lesione dell'onore o della reputazione avviene sulla base di elementi psicologici soggettivi)" +"il diritto morale all'integrità dell'opera (diritto dell'autore originale) non ti consente di apportare all'opera deformazioni o modificazioni, od ogni altro atto a danno dell'opera stessa, che possano essere di pregiudizio all'onore o alla reputazione dell'autore (la valutazione della lesione dell'onore o della reputazione avviene sulla base di elementi psicologici soggettivi)" (dalle faq di Creative Commons Italia) http://www.creativecommons.it/node/165#27 @@ -26,7 +26,7 @@ In particolare, sara' da me considerata lesione d'onore l'uso e/o l'adattamento Se lo fate rischiate la denuncia e il risarcimento danni. Per qualsiasi chiarimento in proposito potete comunque scrivermi. -Questo e' un diritto garantito per legge a me come ad ogni altro artista. +Questo e' un diritto garantito per legge a me come ad ogni altro artista. L'utilizzo della Creative Commons non influisce su questo diritto. ************************************************ @@ -45,7 +45,7 @@ work, so you are not allowed to do exactely what you want with my models. The author (that is me) has the right to prevent distortion, mutilation, or other modification of his work which would be prejudicial to his or her honor or reputation. Me, i consider to be prejudicial to my honor, the modification and/or the use of my models in projects that are related to: - + Racism and hatred instigation. War promotion. Cruelty toward animals. @@ -69,15 +69,15 @@ Note Questo e' stato il mio secondo modello completo. Per questo motivo potrebbe contenere errori e imprecisioni. - + Al momento, questi sono i difetti che ho notato: - + - Non e' nell'origine degli assi. - lo scheletro ha il centro spostato di lato - Nel texture sheet c'e' spazio sprecato. - - + + ################### Notes @@ -86,7 +86,7 @@ This was my first complete model, so it probably contains something wrong. At the moment, this is what i noticed: - + - She's not in the origin of axis - Armature's center is not in armature's center. - Texture sheet contains wasted space. diff --git a/test/models-nonbsd/DXF/rifle.source.txt b/test/models-nonbsd/DXF/rifle.source.txt index a2585afad..fa25f10db 100644 --- a/test/models-nonbsd/DXF/rifle.source.txt +++ b/test/models-nonbsd/DXF/rifle.source.txt @@ -1,7 +1,7 @@ ===================================================================== From http://telias.free.fr -Model copyright: Elias Tsiantas +Model copyright: Elias Tsiantas ===================================================================== @@ -11,9 +11,9 @@ Notice found on the page: " Free the models is a site that offers free 3d models in 3ds, bryce, poser, lightwave and md2 format. Also a great collection of textures to use in -your favorite modelling and rendering program. All the content is free +your favorite modelling and rendering program. All the content is free for any use. In the future more 3d formats will be added and some other -sections such as wallpapers, 3d screensavers, 3d coding source code and +sections such as wallpapers, 3d screensavers, 3d coding source code and tutorials. " diff --git a/test/models-nonbsd/FBX/2013_ASCII/cart_wheel.source.txt b/test/models-nonbsd/FBX/2013_ASCII/cart_wheel.source.txt index 6f43a3a22..bf74fa057 100644 --- a/test/models-nonbsd/FBX/2013_ASCII/cart_wheel.source.txt +++ b/test/models-nonbsd/FBX/2013_ASCII/cart_wheel.source.txt @@ -1,7 +1,7 @@ ===================================================================== From http://telias.free.fr -Model copyright: Elias Tsiantas +Model copyright: Elias Tsiantas ===================================================================== @@ -11,8 +11,8 @@ Notice found on the page: " Free the models is a site that offers free 3d models in 3ds, bryce, poser, lightwave and md2 format. Also a great collection of textures to use in -your favorite modelling and rendering program. All the content is free +your favorite modelling and rendering program. All the content is free for any use. In the future more 3d formats will be added and some other -sections such as wallpapers, 3d screensavers, 3d coding source code and +sections such as wallpapers, 3d screensavers, 3d coding source code and tutorials. " diff --git a/test/models-nonbsd/FBX/2013_ASCII/kwxport_test_vcolors.fbx.source.txt b/test/models-nonbsd/FBX/2013_ASCII/kwxport_test_vcolors.fbx.source.txt index 94ee48e4a..7c81e612f 100644 --- a/test/models-nonbsd/FBX/2013_ASCII/kwxport_test_vcolors.fbx.source.txt +++ b/test/models-nonbsd/FBX/2013_ASCII/kwxport_test_vcolors.fbx.source.txt @@ -1,9 +1,9 @@ From kwxport http://www.kwxport.org/ ->> -The kW Xport plug-in source is released under the MIT license. -Basically, it means "feel free to use it; credit the source; don't sue me +>> +The kW Xport plug-in source is released under the MIT license. +Basically, it means "feel free to use it; credit the source; don't sue me if something goes wrong." >> diff --git a/test/models-nonbsd/FBX/2013_ASCII/mar_rifle.source.txt b/test/models-nonbsd/FBX/2013_ASCII/mar_rifle.source.txt index 6f43a3a22..bf74fa057 100644 --- a/test/models-nonbsd/FBX/2013_ASCII/mar_rifle.source.txt +++ b/test/models-nonbsd/FBX/2013_ASCII/mar_rifle.source.txt @@ -1,7 +1,7 @@ ===================================================================== From http://telias.free.fr -Model copyright: Elias Tsiantas +Model copyright: Elias Tsiantas ===================================================================== @@ -11,8 +11,8 @@ Notice found on the page: " Free the models is a site that offers free 3d models in 3ds, bryce, poser, lightwave and md2 format. Also a great collection of textures to use in -your favorite modelling and rendering program. All the content is free +your favorite modelling and rendering program. All the content is free for any use. In the future more 3d formats will be added and some other -sections such as wallpapers, 3d screensavers, 3d coding source code and +sections such as wallpapers, 3d screensavers, 3d coding source code and tutorials. " diff --git a/test/models-nonbsd/FBX/2013_ASCII/mp5_sil.source.txt b/test/models-nonbsd/FBX/2013_ASCII/mp5_sil.source.txt index 6f43a3a22..bf74fa057 100644 --- a/test/models-nonbsd/FBX/2013_ASCII/mp5_sil.source.txt +++ b/test/models-nonbsd/FBX/2013_ASCII/mp5_sil.source.txt @@ -1,7 +1,7 @@ ===================================================================== From http://telias.free.fr -Model copyright: Elias Tsiantas +Model copyright: Elias Tsiantas ===================================================================== @@ -11,8 +11,8 @@ Notice found on the page: " Free the models is a site that offers free 3d models in 3ds, bryce, poser, lightwave and md2 format. Also a great collection of textures to use in -your favorite modelling and rendering program. All the content is free +your favorite modelling and rendering program. All the content is free for any use. In the future more 3d formats will be added and some other -sections such as wallpapers, 3d screensavers, 3d coding source code and +sections such as wallpapers, 3d screensavers, 3d coding source code and tutorials. " diff --git a/test/models-nonbsd/FBX/2013_BINARY/cart_wheel.source.txt b/test/models-nonbsd/FBX/2013_BINARY/cart_wheel.source.txt index 6f43a3a22..bf74fa057 100644 --- a/test/models-nonbsd/FBX/2013_BINARY/cart_wheel.source.txt +++ b/test/models-nonbsd/FBX/2013_BINARY/cart_wheel.source.txt @@ -1,7 +1,7 @@ ===================================================================== From http://telias.free.fr -Model copyright: Elias Tsiantas +Model copyright: Elias Tsiantas ===================================================================== @@ -11,8 +11,8 @@ Notice found on the page: " Free the models is a site that offers free 3d models in 3ds, bryce, poser, lightwave and md2 format. Also a great collection of textures to use in -your favorite modelling and rendering program. All the content is free +your favorite modelling and rendering program. All the content is free for any use. In the future more 3d formats will be added and some other -sections such as wallpapers, 3d screensavers, 3d coding source code and +sections such as wallpapers, 3d screensavers, 3d coding source code and tutorials. " diff --git a/test/models-nonbsd/FBX/2013_BINARY/kwxport_test_vcolors.fbx.source.txt b/test/models-nonbsd/FBX/2013_BINARY/kwxport_test_vcolors.fbx.source.txt index 94ee48e4a..7c81e612f 100644 --- a/test/models-nonbsd/FBX/2013_BINARY/kwxport_test_vcolors.fbx.source.txt +++ b/test/models-nonbsd/FBX/2013_BINARY/kwxport_test_vcolors.fbx.source.txt @@ -1,9 +1,9 @@ From kwxport http://www.kwxport.org/ ->> -The kW Xport plug-in source is released under the MIT license. -Basically, it means "feel free to use it; credit the source; don't sue me +>> +The kW Xport plug-in source is released under the MIT license. +Basically, it means "feel free to use it; credit the source; don't sue me if something goes wrong." >> diff --git a/test/models-nonbsd/FBX/2013_BINARY/mar_rifle.source.txt b/test/models-nonbsd/FBX/2013_BINARY/mar_rifle.source.txt index 6f43a3a22..bf74fa057 100644 --- a/test/models-nonbsd/FBX/2013_BINARY/mar_rifle.source.txt +++ b/test/models-nonbsd/FBX/2013_BINARY/mar_rifle.source.txt @@ -1,7 +1,7 @@ ===================================================================== From http://telias.free.fr -Model copyright: Elias Tsiantas +Model copyright: Elias Tsiantas ===================================================================== @@ -11,8 +11,8 @@ Notice found on the page: " Free the models is a site that offers free 3d models in 3ds, bryce, poser, lightwave and md2 format. Also a great collection of textures to use in -your favorite modelling and rendering program. All the content is free +your favorite modelling and rendering program. All the content is free for any use. In the future more 3d formats will be added and some other -sections such as wallpapers, 3d screensavers, 3d coding source code and +sections such as wallpapers, 3d screensavers, 3d coding source code and tutorials. " diff --git a/test/models-nonbsd/FBX/2013_BINARY/mp5_sil.source.txt b/test/models-nonbsd/FBX/2013_BINARY/mp5_sil.source.txt index 6f43a3a22..bf74fa057 100644 --- a/test/models-nonbsd/FBX/2013_BINARY/mp5_sil.source.txt +++ b/test/models-nonbsd/FBX/2013_BINARY/mp5_sil.source.txt @@ -1,7 +1,7 @@ ===================================================================== From http://telias.free.fr -Model copyright: Elias Tsiantas +Model copyright: Elias Tsiantas ===================================================================== @@ -11,8 +11,8 @@ Notice found on the page: " Free the models is a site that offers free 3d models in 3ds, bryce, poser, lightwave and md2 format. Also a great collection of textures to use in -your favorite modelling and rendering program. All the content is free +your favorite modelling and rendering program. All the content is free for any use. In the future more 3d formats will be added and some other -sections such as wallpapers, 3d screensavers, 3d coding source code and +sections such as wallpapers, 3d screensavers, 3d coding source code and tutorials. " diff --git a/test/models-nonbsd/LWO/LWO2/LWSReferences/QuickDraw.source.txt b/test/models-nonbsd/LWO/LWO2/LWSReferences/QuickDraw.source.txt index aaa244217..80afd30a7 100644 --- a/test/models-nonbsd/LWO/LWO2/LWSReferences/QuickDraw.source.txt +++ b/test/models-nonbsd/LWO/LWO2/LWSReferences/QuickDraw.source.txt @@ -1,10 +1,10 @@ ===================================================================== From http://telias.free.fr -Model copyright: Elias Tsiantas +Model copyright: Elias Tsiantas -"These 3d models are contributed by John Hoffman and are based on -characters from a cartoon show called "Jayce and the wheel warriors" +"These 3d models are contributed by John Hoffman and are based on +characters from a cartoon show called "Jayce and the wheel warriors" (except the marauder) John's site: http://www3.sympatico.ca/john.hoffman" ===================================================================== @@ -15,9 +15,9 @@ Notice found on the page: " Free the models is a site that offers free 3d models in 3ds, bryce, poser, lightwave and md2 format. Also a great collection of textures to use in -your favorite modelling and rendering program. All the content is free +your favorite modelling and rendering program. All the content is free for any use. In the future more 3d formats will be added and some other -sections such as wallpapers, 3d screensavers, 3d coding source code and +sections such as wallpapers, 3d screensavers, 3d coding source code and tutorials. " diff --git a/test/models-nonbsd/LWO/LWO2/rifle.source.txt b/test/models-nonbsd/LWO/LWO2/rifle.source.txt index 5523bbc0c..5774ecc0e 100644 --- a/test/models-nonbsd/LWO/LWO2/rifle.source.txt +++ b/test/models-nonbsd/LWO/LWO2/rifle.source.txt @@ -1,7 +1,7 @@ ===================================================================== From http://telias.free.fr -Model copyright: Elias Tsiantas +Model copyright: Elias Tsiantas ===================================================================== @@ -11,9 +11,9 @@ Notice found on the page: " Free the models is a site that offers free 3d models in 3ds, bryce, poser, lightwave and md2 format. Also a great collection of textures to use in -your favorite modelling and rendering program. All the content is free +your favorite modelling and rendering program. All the content is free for any use. In the future more 3d formats will be added and some other -sections such as wallpapers, 3d screensavers, 3d coding source code and +sections such as wallpapers, 3d screensavers, 3d coding source code and tutorials. " diff --git a/test/models-nonbsd/MD2/source.txt b/test/models-nonbsd/MD2/source.txt index 6f43a3a22..bf74fa057 100644 --- a/test/models-nonbsd/MD2/source.txt +++ b/test/models-nonbsd/MD2/source.txt @@ -1,7 +1,7 @@ ===================================================================== From http://telias.free.fr -Model copyright: Elias Tsiantas +Model copyright: Elias Tsiantas ===================================================================== @@ -11,8 +11,8 @@ Notice found on the page: " Free the models is a site that offers free 3d models in 3ds, bryce, poser, lightwave and md2 format. Also a great collection of textures to use in -your favorite modelling and rendering program. All the content is free +your favorite modelling and rendering program. All the content is free for any use. In the future more 3d formats will be added and some other -sections such as wallpapers, 3d screensavers, 3d coding source code and +sections such as wallpapers, 3d screensavers, 3d coding source code and tutorials. " diff --git a/test/models-nonbsd/MD5/BoarMan.source.txt b/test/models-nonbsd/MD5/BoarMan.source.txt index 50b2dfb53..69f0c79a2 100644 --- a/test/models-nonbsd/MD5/BoarMan.source.txt +++ b/test/models-nonbsd/MD5/BoarMan.source.txt @@ -1,8 +1,8 @@ -License: Creative Commons +License: Creative Commons - Remix - Share alike - Attribution Author: zphr (Christian Lenke) - + diff --git a/test/models-nonbsd/MDL/IDPO (Quake1)/gijoe-readme.txt b/test/models-nonbsd/MDL/IDPO (Quake1)/gijoe-readme.txt index 2febb0583..84ce9fd95 100644 --- a/test/models-nonbsd/MDL/IDPO (Quake1)/gijoe-readme.txt +++ b/test/models-nonbsd/MDL/IDPO (Quake1)/gijoe-readme.txt @@ -4,9 +4,9 @@ Version : 1 Date : 11/05/97 Author : Kenneth Whelan Email : JWHELAN@pop.prodigy.net -Credits : id software, Larry Hama, Steven Polge, and Rene Post for making Quake ME - - +Credits : id software, Larry Hama, Steven Polge, and Rene Post for making Quake ME + + Build time: ??? Time??? @@ -14,7 +14,7 @@ Type of Mod ----------- Quake C : no Sound : no -MDL : Yes +MDL : Yes Format of QuakeC (if a Quake C Mod) @@ -29,8 +29,8 @@ Description of the Modification ------------------------------- This is a new player.mdl for quake. It's main use is for bots. The Skins are Snake Eyes v4, Duke v3, Low-Light, -Storm Shadow v2, Shockwave, Repeater, Gung-Ho, Shipwreck, Dusty v3, and -Tunnel Rat v2. +Storm Shadow v2, Shockwave, Repeater, Gung-Ho, Shipwreck, Dusty v3, and +Tunnel Rat v2. @@ -40,7 +40,7 @@ None that I know of. How to Install the Modification ------------------------------- -First back up the current player.mdl(copy player.mdl player.bak). Just put +First back up the current player.mdl(copy player.mdl player.bak). Just put it in the progs dir in the hack your using, if any. Technical Details diff --git a/test/models-nonbsd/MDL/IDPO (Quake1)/steg.txt b/test/models-nonbsd/MDL/IDPO (Quake1)/steg.txt index c07c2f126..cbddccb7e 100644 --- a/test/models-nonbsd/MDL/IDPO (Quake1)/steg.txt +++ b/test/models-nonbsd/MDL/IDPO (Quake1)/steg.txt @@ -16,17 +16,17 @@ E-mail: sgalbrai@linknet.kitsap.lib.wa.us WWW: www.oz.net/~simitar -This model can be used or modified for any purpose +This model can be used or modified for any purpose as long as this text document is included with it -and all modellers listed in this document are +and all modellers listed in this document are listed wherever credits are appropriate for that purpose. -Help Wanted: +Help Wanted: The Free Models Project can use help with -models and in other areas, such as legal boilerplate +models and in other areas, such as legal boilerplate for models. WWW: www.oz.net/~simitar/model.html diff --git a/test/models-nonbsd/MDL/IDPO (Quake1)/tekmechbot.txt b/test/models-nonbsd/MDL/IDPO (Quake1)/tekmechbot.txt index dc0149b01..3b3dd9042 100644 --- a/test/models-nonbsd/MDL/IDPO (Quake1)/tekmechbot.txt +++ b/test/models-nonbsd/MDL/IDPO (Quake1)/tekmechbot.txt @@ -8,7 +8,7 @@ that the FMP is a great concept !! There is some movement in the model. The legs can be animated for walking, stomping, or running -around ! +around ! Ok, it's my first model, so I will work on less polygony in the future ;-) @@ -18,9 +18,9 @@ Contact: ebuy@optelnow.net (E-MAIL) Date Created: 7/07/2000 ====================================================== -This model can be used or modified for any purpose +This model can be used or modified for any purpose as long as this text document is included with it -and all modelers listed in this document are +and all modelers listed in this document are listed wherever credits are appropriate for that purpose. diff --git a/test/models-nonbsd/NFF/NFFSense8/credits.txt b/test/models-nonbsd/NFF/NFFSense8/credits.txt index f3cef4d09..bad7fbf15 100644 --- a/test/models-nonbsd/NFF/NFFSense8/credits.txt +++ b/test/models-nonbsd/NFF/NFFSense8/credits.txt @@ -1,4 +1,4 @@ teapot.nff, home4.nff - http://www.martinreddy.net/ukvrsig/wtk.html -cokecan.nff -www.vrupl.evl.uic.edu/Eng591_Pages/cokecan.nff +cokecan.nff -www.vrupl.evl.uic.edu/Eng591_Pages/cokecan.nff TODO: License status to be confirmed diff --git a/test/models-nonbsd/OBJ/rifle.source.txt b/test/models-nonbsd/OBJ/rifle.source.txt index 1d2cec5cf..f7b93fd0f 100644 --- a/test/models-nonbsd/OBJ/rifle.source.txt +++ b/test/models-nonbsd/OBJ/rifle.source.txt @@ -1,7 +1,7 @@ ===================================================================== From http://telias.free.fr -Model copyright: Elias Tsiantas +Model copyright: Elias Tsiantas ===================================================================== @@ -11,9 +11,9 @@ Notices found on the page: " Free the models is a site that offers free 3d models in 3ds, bryce, poser, lightwave and md2 format. Also a great collection of textures to use in -your favorite modelling and rendering program. All the content is free +your favorite modelling and rendering program. All the content is free for any use. In the future more 3d formats will be added and some other -sections such as wallpapers, 3d screensavers, 3d coding source code and +sections such as wallpapers, 3d screensavers, 3d coding source code and tutorials. " diff --git a/test/models/3DS/UVTransformTest/note.txt b/test/models/3DS/UVTransformTest/note.txt index 4c8bfedd2..9a6bab030 100644 --- a/test/models/3DS/UVTransformTest/note.txt +++ b/test/models/3DS/UVTransformTest/note.txt @@ -1,5 +1,5 @@ -All 'mirror' files are not absolutely correct. That's mainly -because it's difficult convert Max' handling of mirroring to +All 'mirror' files are not absolutely correct. That's mainly +because it's difficult convert Max' handling of mirroring to our's. In other words: TO DO, but only if someone REALLY needs it. diff --git a/test/models/ASE/MotionCaptureROM.source.txt b/test/models/ASE/MotionCaptureROM.source.txt index 2b961906a..f34e056ef 100644 --- a/test/models/ASE/MotionCaptureROM.source.txt +++ b/test/models/ASE/MotionCaptureROM.source.txt @@ -1,3 +1,3 @@ -"MotionCaptureROM.ase" - Free for any purpose. +"MotionCaptureROM.ase" - Free for any purpose. NOTE: The errors in the middle of the animation are caused by problems during recording, it's not an importer issue. diff --git a/test/models/Collada/human.zae b/test/models/Collada/human.zae new file mode 100644 index 0000000000000000000000000000000000000000..691b09f83f6ac2baf2d6bb55026da9b95470c536 GIT binary patch literal 1093924 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

    uuGR&r@9o{lX?*e|=%DZ$(yMTN$V&+tmT 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

    *MMruzAc9`aeV4mOVXe!g)p!=rmE|XzS3l&x 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 z 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 |%F(BJFHCq3pF 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

    YBvu9UVk?n=SJ=$+2ziatfpq&?O74_ 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

    Qf((sXQc*MjpgC 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≫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�{S+3fZ-hMfu{#Z(yVPGkh*hYRfc)(cg0-9cef(Gw5tG+NE2Qh^& ztn7IO9SF_lhQ#^Xu-4lA@w?1C*fm)?-Ih3ADgxl+02u&d5~R(=gJvn-eXBY|0#nL|?fEGq~nPBe0rc`&HSHokHof$WLnlDMjPa#5ehL_B8LV08NZr_N3B&PWXOT zw3X1Xd?+5>uv_$}SqaF?hW8_oK=r>_9Ax@4DX5U(e5~&-N?Dn{;Q`p$0^TUY#f&)$M0k18G&xvcFlzz!Te5_JRgsx&DG9es5B))aLSM zY+}Tn@M@1ArC8+B*AM5Knh6^hm(rW2Vld8%J&W?Fhf(jDY4|&L?=6ToG3YC&@1D?Q zlEmjDM6Xbbmq7FhezYvE)P3y7i;32~SpRIm_64h9(v3v!ro)2fE?Lj&J}BMp8(;0Fxz=oe zF;kA`r|B*+;-hm_KDbUC*vD!_)QKet?9ddMabk*e2e*zyZXafUA+z0LsAYxY)RH5? zpCZ_gngRlS<4m5z2|1syb9YBCs2tl(m*R&~o%*JV3(mOIab?eHO@8FT$-o%I z$sh(*%mCI_3pA`GwW6f7D79EWsVKiZFFv(0Rj(i~J;0ll4WyI_2&;kg> -The kW Xport plug-in source is released under the MIT license. -Basically, it means "feel free to use it; credit the source; don't sue me +>> +The kW Xport plug-in source is released under the MIT license. +Basically, it means "feel free to use it; credit the source; don't sue me if something goes wrong." >> diff --git a/test/models/IRR/warn_dwarf_scaling_is_intended.txt b/test/models/IRR/warn_dwarf_scaling_is_intended.txt index f651a8643..d0e4ea361 100644 --- a/test/models/IRR/warn_dwarf_scaling_is_intended.txt +++ b/test/models/IRR/warn_dwarf_scaling_is_intended.txt @@ -1,3 +1,3 @@ for dawfInCellar_ChildOfCellar & dawfInCellar_SameHierarchy: -the strange scalings of cellar and dwarf are intended. +the strange scalings of cellar and dwarf are intended. diff --git a/test/models/MD2/faerie-source.txt b/test/models/MD2/faerie-source.txt index 4906ff4d1..afb00a170 100644 --- a/test/models/MD2/faerie-source.txt +++ b/test/models/MD2/faerie-source.txt @@ -1,5 +1,5 @@ -From IRRLICHT/media +From IRRLICHT/media The Irrlicht Engine License diff --git a/test/models/MD2/sidney-source.txt b/test/models/MD2/sidney-source.txt index 4906ff4d1..afb00a170 100644 --- a/test/models/MD2/sidney-source.txt +++ b/test/models/MD2/sidney-source.txt @@ -1,5 +1,5 @@ -From IRRLICHT/media +From IRRLICHT/media The Irrlicht Engine License diff --git a/test/models/Q3D/E-AT-AT.source.txt b/test/models/Q3D/E-AT-AT.source.txt index 2df8826f1..900ee552c 100644 --- a/test/models/Q3D/E-AT-AT.source.txt +++ b/test/models/Q3D/E-AT-AT.source.txt @@ -5,4 +5,4 @@ Downloaded 4th November 08 (Obama ftw!) Copyright notice found on the page: Where do the models in the archive come from? -All 3D files available from the3darchive.com are from the public domain. +All 3D files available from the3darchive.com are from the public domain. diff --git a/test/models/Q3D/earth.source.txt b/test/models/Q3D/earth.source.txt index 2df8826f1..900ee552c 100644 --- a/test/models/Q3D/earth.source.txt +++ b/test/models/Q3D/earth.source.txt @@ -5,4 +5,4 @@ Downloaded 4th November 08 (Obama ftw!) Copyright notice found on the page: Where do the models in the archive come from? -All 3D files available from the3darchive.com are from the public domain. +All 3D files available from the3darchive.com are from the public domain. diff --git a/test/models/WRL/credits.txt b/test/models/WRL/credits.txt index 055f73734..7be7fa192 100644 --- a/test/models/WRL/credits.txt +++ b/test/models/WRL/credits.txt @@ -1,2 +1,2 @@ "MotionCaptureROM.ase" Recorded using ViconIQ. -Converted to VRML with 3DS Max 2008. +Converted to VRML with 3DS Max 2008. diff --git a/test/models/X/anim_test.txt b/test/models/X/anim_test.txt index 3d270dc7d..f2ef6c056 100644 --- a/test/models/X/anim_test.txt +++ b/test/models/X/anim_test.txt @@ -4,6 +4,6 @@ Frame 1 - 10: Zylinder knickt ein, so dass der Knick in Richtung z+ zeigt. Frame 10 - 18: Zylinder-Spitze streckt sich in Richtung z-. Frame 18 - 24: Zylinder-Spitze bewegt sich zu Position in Richtung x+ -Remarks: The exporter failed here for some reasons... although the mesh referres to four bones, only two of them are stored in the corresponding node hierarchy. So you have a mesh with 4 bones, a hirarchy with 2 nodes and a animation that affects only those two nodes. +Remarks: The exporter failed here for some reasons... although the mesh referres to four bones, only two of them are stored in the corresponding node hierarchy. So you have a mesh with 4 bones, a hirarchy with 2 nodes and a animation that affects only those two nodes. There is no timing given for the animation. You have to scale the animation manually. For this file, the timing seems to be 24 ticks per second. diff --git a/test/models/X/kwxport_test_cubewithvcolors.source.txt b/test/models/X/kwxport_test_cubewithvcolors.source.txt index 94ee48e4a..7c81e612f 100644 --- a/test/models/X/kwxport_test_cubewithvcolors.source.txt +++ b/test/models/X/kwxport_test_cubewithvcolors.source.txt @@ -1,9 +1,9 @@ From kwxport http://www.kwxport.org/ ->> -The kW Xport plug-in source is released under the MIT license. -Basically, it means "feel free to use it; credit the source; don't sue me +>> +The kW Xport plug-in source is released under the MIT license. +Basically, it means "feel free to use it; credit the source; don't sue me if something goes wrong." >> diff --git a/test/models/X/test.txt b/test/models/X/test.txt index eaf9d9c3c..9452855b3 100644 --- a/test/models/X/test.txt +++ b/test/models/X/test.txt @@ -1,3 +1,3 @@ Simple textured test cube exported from Maya. Has a texture that does label each cube side uniquely, but the sides do not match DirectX coordinate space. -Is not readable using D3DXLoadFrameHierarchy, needs custom text parsing. +Is not readable using D3DXLoadFrameHierarchy, needs custom text parsing. diff --git a/test/models/glTF2/ClearCoat-glTF/ClearCoatLabels.png b/test/models/glTF2/ClearCoat-glTF/ClearCoatLabels.png new file mode 100644 index 0000000000000000000000000000000000000000..d47f2f5e1f06116fca5ddd9697001ffedcc5bd68 GIT binary patch literal 10270 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 literal 0 HcmV?d00001 diff --git a/test/models/glTF2/ClearCoat-glTF/ClearCoatTest.bin b/test/models/glTF2/ClearCoat-glTF/ClearCoatTest.bin new file mode 100644 index 0000000000000000000000000000000000000000..e7c6a93a4995917f931d580f7d25c94f33d1639a GIT binary patch literal 50328 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 z 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 MV6NHY-@g5S0BBn6`Tzg` literal 0 HcmV?d00001 diff --git a/test/models/glTF2/ClearCoat-glTF/ClearCoatTest.gltf b/test/models/glTF2/ClearCoat-glTF/ClearCoatTest.gltf new file mode 100644 index 000000000..ae12bfc11 --- /dev/null +++ b/test/models/glTF2/ClearCoat-glTF/ClearCoatTest.gltf @@ -0,0 +1,1669 @@ +{ + "asset" : { + "generator" : "Khronos glTF Blender I/O v1.2.8 with hand-edits for clearcoat", + "version" : "2.0" + }, + "extensionsUsed": [ + "KHR_materials_clearcoat" + ], + "scene" : 0, + "scenes" : [ + { + "name" : "Scene", + "nodes" : [ + 3, + 7, + 11, + 15, + 19, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32 + ] + } + ], + "nodes" : [ + { + "mesh" : 0, + "name" : "BaseLayerSample", + "translation" : [ + -2.0999999046325684, + 0, + 0 + ] + }, + { + "mesh" : 1, + "name" : "ClearCoatSample" + }, + { + "mesh" : 2, + "name" : "CoatOnlySample", + "translation" : [ + 2.0999999046325684, + 0, + 0 + ] + }, + { + "children" : [ + 0, + 1, + 2 + ], + "name" : "R0_SimpleCoatTest", + "translation" : [ + 0, + 5.25, + 0 + ] + }, + { + "mesh" : 3, + "name" : "R1_BaseLayerSample", + "translation" : [ + -2.0999999046325684, + 0, + 0 + ] + }, + { + "mesh" : 4, + "name" : "R1_ClearCoatSample" + }, + { + "mesh" : 5, + "name" : "R1_CoatOnlySample", + "translation" : [ + 2.0999999046325684, + 0, + 0 + ] + }, + { + "children" : [ + 4, + 5, + 6 + ], + "name" : "R1_PartialCoatTest", + "translation" : [ + 0, + 3.1500000953674316, + 0 + ] + }, + { + "mesh" : 6, + "name" : "R2_BaseLayerSample", + "translation" : [ + -2.0999999046325684, + 0, + 0 + ] + }, + { + "mesh" : 7, + "name" : "R2_ClearCoatSample" + }, + { + "mesh" : 8, + "name" : "R2_CoatOnlySample", + "translation" : [ + 2.0999999046325684, + 0, + 0 + ] + }, + { + "children" : [ + 8, + 9, + 10 + ], + "name" : "R2_RoughnessVariations", + "translation" : [ + 0, + 1.0499999523162842, + 0 + ] + }, + { + "mesh" : 9, + "name" : "R3_BaseLayerSample", + "translation" : [ + -2.0999999046325684, + 0, + 0 + ] + }, + { + "mesh" : 10, + "name" : "R3_ClearCoatSample" + }, + { + "mesh" : 11, + "name" : "R3_CoatOnlySample", + "translation" : [ + 2.0999999046325684, + 0, + 0 + ] + }, + { + "children" : [ + 12, + 13, + 14 + ], + "name" : "R3_BaseNormals", + "translation" : [ + 0, + -1.0499999523162842, + 0 + ] + }, + { + "mesh" : 12, + "name" : "R4_BaseLayerSample", + "translation" : [ + -2.0999999046325684, + 0, + 0 + ] + }, + { + "mesh" : 13, + "name" : "R4_ClearCoatSample" + }, + { + "mesh" : 14, + "name" : "R4_CoatOnlySample", + "translation" : [ + 2.0999999046325684, + 0, + 0 + ] + }, + { + "children" : [ + 16, + 17, + 18 + ], + "name" : "R4_CoatNormals", + "translation" : [ + 0, + -5.25, + 0 + ] + }, + { + "mesh" : 15, + "name" : "R5_BaseLayerSample", + "translation" : [ + -2.0999999046325684, + 0, + 0 + ] + }, + { + "mesh" : 16, + "name" : "R5_ClearCoatSample" + }, + { + "mesh" : 17, + "name" : "R5_CoatOnlySample", + "translation" : [ + 2.0999999046325684, + 0, + 0 + ] + }, + { + "children" : [ + 20, + 21, + 22 + ], + "name" : "R5_SharedNormals", + "translation" : [ + 0, + -3.1500000953674316, + 0 + ] + }, + { + "mesh" : 18, + "name" : "X2_Label_CoatingOnly", + "translation" : [ + 2.0712804794311523, + 6.619500160217285, + 0 + ] + }, + { + "mesh" : 19, + "name" : "Y0_Label_SimpleCoating", + "translation" : [ + -5.3578033447265625, + 5.25, + 0 + ] + }, + { + "mesh" : 20, + "name" : "Y1_Label_PartialCoating", + "translation" : [ + -5.3578033447265625, + 3.1673200130462646, + 0 + ] + }, + { + "mesh" : 21, + "name" : "Y2_Label_Roughness", + "translation" : [ + -5.3578033447265625, + 1.1383899450302124, + 0 + ] + }, + { + "mesh" : 22, + "name" : "Y3_Label_BaseNormals", + "translation" : [ + -5.3578033447265625, + -1.099429965019226, + 0 + ] + }, + { + "mesh" : 23, + "name" : "Y4_Label_CoatNormals", + "translation" : [ + -5.3578033447265625, + -5.252500057220459, + 0 + ] + }, + { + "mesh" : 24, + "name" : "Y5_Label_SharedNormals", + "translation" : [ + -5.3578033447265625, + -3.1963000297546387, + 0 + ] + }, + { + "mesh" : 25, + "name" : "X0_Label_BaseLayer", + "translation" : [ + -2.087031602859497, + 6.616230010986328, + 0 + ] + }, + { + "mesh" : 26, + "name" : "X1_Label_Coated", + "translation" : [ + 0, + 6.614150047302246, + 0 + ] + } + ], + "materials" : [ + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Simple_Base", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.5, + 0.019999999552965164, + 0.009999999776482582, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Simple_Coated", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.5, + 0.019999999552965164, + 0.009999999776482582, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 1, + "clearcoatRoughnessFactor": 0.03 + } + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Simple_Coating", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0, + 0, + 0, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.029999999329447746 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Partial_Base", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Partial_Coated", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 1, + "clearcoatRoughnessFactor": 0.03, + "clearcoatTexture": { + "index": 0, + "texCoord": 0 + } + } + } + }, + { + "alphaMode" : "BLEND", + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Partial_Coating", + "pbrMetallicRoughness" : { + "baseColorTexture" : { + "index" : 1, + "texCoord" : 0 + }, + "metallicFactor" : 0, + "roughnessFactor" : 0.029999999329447746 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "RoughVariations_Base", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.6000000238418579 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "RoughVariations_Coated", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.6000000238418579 + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 1, + "clearcoatRoughnessFactor": 1, + "clearcoatRoughnessTexture": { + "index": 2, + "texCoord": 0 + } + } + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "RoughVariations_Coating", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0, + 0, + 0, + 1 + ], + "metallicFactor" : 0, + "metallicRoughnessTexture" : { + "index" : 2, + "texCoord" : 0 + } + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "BaseNorm_Base", + "normalTexture" : { + "index" : 3, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "BaseNorm_Coated", + "normalTexture" : { + "index" : 4, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012848637998104095, + 0.021861059591174126, + 0.11068868637084961, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 1, + "clearcoatRoughnessFactor": 0.03 + } + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "BaseNorm_Coating", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0, + 0, + 0, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.029999999329447746 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "CoatNorm_Base", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "CoatNorm_Coated", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 1, + "clearcoatRoughnessFactor": 0.03, + "clearcoatNormalTexture": { + "index": 5, + "texCoord": 0, + "scale": 1 + } + } + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "CoatNorm_Coating", + "normalTexture" : { + "index" : 5, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0, + 0, + 0, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.029999999329447746 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "SharedNorm_Base", + "normalTexture" : { + "index" : 6, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "SharedNorm_Coated", + "normalTexture" : { + "index" : 7, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.012983020395040512, + 0.022173883393406868, + 0.10946174710988998, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4399999976158142 + }, + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 1, + "clearcoatRoughnessFactor": 0.03, + "clearcoatNormalTexture": { + "index": 7, + "texCoord": 0 + } + } + } + }, + { + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "SharedNorm_Coating", + "normalTexture" : { + "index" : 8, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0, + 0, + 0, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.029999999329447746 + } + }, + { + "emissiveFactor" : [ + 1, + 1, + 1 + ], + "emissiveTexture" : { + "index" : 9, + "texCoord" : 0 + }, + "name" : "LabelMaterial", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0, + 0, + 0, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.8999999761581421 + } + } + ], + "meshes" : [ + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 0 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 1 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 2 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 3 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 4 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 5 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 6 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 7 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 8 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 9 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 10 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 11 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 12 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 13 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 14 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 15 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 16 + } + ] + }, + { + "name" : "ClearCoatSampleMesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 17 + } + ] + }, + { + "name" : "Labels_Mesh", + "primitives" : [ + { + "attributes" : { + "POSITION" : 4, + "NORMAL" : 5, + "TEXCOORD_0" : 6 + }, + "indices" : 7, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.001", + "primitives" : [ + { + "attributes" : { + "POSITION" : 8, + "NORMAL" : 9, + "TEXCOORD_0" : 10 + }, + "indices" : 7, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.002", + "primitives" : [ + { + "attributes" : { + "POSITION" : 11, + "NORMAL" : 12, + "TEXCOORD_0" : 13 + }, + "indices" : 14, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.003", + "primitives" : [ + { + "attributes" : { + "POSITION" : 15, + "NORMAL" : 16, + "TEXCOORD_0" : 17 + }, + "indices" : 14, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.004", + "primitives" : [ + { + "attributes" : { + "POSITION" : 18, + "NORMAL" : 19, + "TEXCOORD_0" : 20 + }, + "indices" : 14, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.005", + "primitives" : [ + { + "attributes" : { + "POSITION" : 21, + "NORMAL" : 22, + "TEXCOORD_0" : 23 + }, + "indices" : 14, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.006", + "primitives" : [ + { + "attributes" : { + "POSITION" : 24, + "NORMAL" : 25, + "TEXCOORD_0" : 26 + }, + "indices" : 14, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.007", + "primitives" : [ + { + "attributes" : { + "POSITION" : 27, + "NORMAL" : 28, + "TEXCOORD_0" : 29 + }, + "indices" : 14, + "material" : 18 + } + ] + }, + { + "name" : "Labels_Mesh.008", + "primitives" : [ + { + "attributes" : { + "POSITION" : 30, + "NORMAL" : 31, + "TEXCOORD_0" : 32 + }, + "indices" : 7, + "material" : 18 + } + ] + } + ], + "textures" : [ + { + "source" : 0 + }, + { + "source" : 1 + }, + { + "source" : 2 + }, + { + "source" : 3 + }, + { + "source" : 3 + }, + { + "source" : 4 + }, + { + "source" : 3 + }, + { + "source" : 3 + }, + { + "source" : 3 + }, + { + "source" : 5 + } + ], + "images" : [ + { + "mimeType" : "image/png", + "name" : "PartialCoating", + "uri" : "PartialCoating.png" + }, + { + "mimeType" : "image/png", + "name" : "PartialCoating_Alpha", + "uri" : "PartialCoating_Alpha.png" + }, + { + "mimeType" : "image/png", + "name" : "RoughnessStripes", + "uri" : "RoughnessStripes.png" + }, + { + "mimeType" : "image/png", + "name" : "RibsNormal", + "uri" : "RibsNormal.png" + }, + { + "mimeType" : "image/jpeg", + "name" : "PlasticWrap_normals", + "uri" : "PlasticWrap_normals.jpg" + }, + { + "mimeType" : "image/png", + "name" : "ClearCoatLabels", + "uri" : "ClearCoatLabels.png" + } + ], + "accessors" : [ + { + "bufferView" : 0, + "componentType" : 5126, + "count" : 1113, + "max" : [ + 1, + 1, + 1.0499999523162842 + ], + "min" : [ + -1, + -1, + -0.06000000983476639 + ], + "type" : "VEC3" + }, + { + "bufferView" : 1, + "componentType" : 5126, + "count" : 1113, + "type" : "VEC3" + }, + { + "bufferView" : 2, + "componentType" : 5126, + "count" : 1113, + "type" : "VEC2" + }, + { + "bufferView" : 3, + "componentType" : 5123, + "count" : 6180, + "type" : "SCALAR" + }, + { + "bufferView" : 4, + "componentType" : 5126, + "count" : 8, + "max" : [ + 1.0280373096466064, + 0.23501670360565186, + 3.8289083903464416e-08 + ], + "min" : [ + -0.968224287033081, + -0.2350165843963623, + -0.010000125505030155 + ], + "type" : "VEC3" + }, + { + "bufferView" : 5, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 6, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 7, + "componentType" : 5123, + "count" : 12, + "type" : "SCALAR" + }, + { + "bufferView" : 8, + "componentType" : 5126, + "count" : 8, + "max" : [ + 2, + 0.23026323318481445, + 3.751463495405005e-08 + ], + "min" : [ + -2, + -0.23026317358016968, + -0.010000579059123993 + ], + "type" : "VEC3" + }, + { + "bufferView" : 9, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 10, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 11, + "componentType" : 5126, + "count" : 8, + "max" : [ + 2, + 0.2302631139755249, + 3.7514624295909016e-08 + ], + "min" : [ + -2, + -0.23026323318481445, + -0.010000428184866905 + ], + "type" : "VEC3" + }, + { + "bufferView" : 12, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 13, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 14, + "componentType" : 5123, + "count" : 12, + "type" : "SCALAR" + }, + { + "bufferView" : 15, + "componentType" : 5126, + "count" : 8, + "max" : [ + 2, + 0.22039484977722168, + 3.590687924770464e-08 + ], + "min" : [ + -2, + -0.22039473056793213, + -0.010000280104577541 + ], + "type" : "VEC3" + }, + { + "bufferView" : 16, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 17, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 18, + "componentType" : 5126, + "count" : 8, + "max" : [ + 2, + 0.21764808893203735, + 3.545937588000925e-08 + ], + "min" : [ + -2, + -0.21764802932739258, + -0.010000137612223625 + ], + "type" : "VEC3" + }, + { + "bufferView" : 19, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 20, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 21, + "componentType" : 5126, + "count" : 8, + "max" : [ + 2, + 0.20775499939918518, + 3.3847587843638394e-08 + ], + "min" : [ + -2, + -0.20775499939918518, + -0.009999996051192284 + ], + "type" : "VEC3" + }, + { + "bufferView" : 22, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 23, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 24, + "componentType" : 5126, + "count" : 8, + "max" : [ + 2, + 0.22341907024383545, + 3.6399587344249085e-08 + ], + "min" : [ + -2, + -0.22341907024383545, + -0.009999859146773815 + ], + "type" : "VEC3" + }, + { + "bufferView" : 25, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 26, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 27, + "componentType" : 5126, + "count" : 8, + "max" : [ + 0.9169960618019104, + 0.22670458257198334, + 3.69348676088066e-08 + ], + "min" : [ + -0.9199233651161194, + -0.22670456767082214, + -0.010000176727771759 + ], + "type" : "VEC3" + }, + { + "bufferView" : 28, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 29, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + }, + { + "bufferView" : 30, + "componentType" : 5126, + "count" : 8, + "max" : [ + 0.8968609571456909, + 0.20853587985038757, + 3.397480696776256e-08 + ], + "min" : [ + -0.9147982001304626, + -0.2085357904434204, + -0.010000113397836685 + ], + "type" : "VEC3" + }, + { + "bufferView" : 31, + "componentType" : 5126, + "count" : 8, + "type" : "VEC3" + }, + { + "bufferView" : 32, + "componentType" : 5126, + "count" : 8, + "type" : "VEC2" + } + ], + "bufferViews" : [ + { + "buffer" : 0, + "byteLength" : 13356, + "byteOffset" : 0 + }, + { + "buffer" : 0, + "byteLength" : 13356, + "byteOffset" : 13356 + }, + { + "buffer" : 0, + "byteLength" : 8904, + "byteOffset" : 26712 + }, + { + "buffer" : 0, + "byteLength" : 12360, + "byteOffset" : 35616 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 47976 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48072 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 48168 + }, + { + "buffer" : 0, + "byteLength" : 24, + "byteOffset" : 48232 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48256 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48352 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 48448 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48512 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48608 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 48704 + }, + { + "buffer" : 0, + "byteLength" : 24, + "byteOffset" : 48768 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48792 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 48888 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 48984 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49048 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49144 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 49240 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49304 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49400 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 49496 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49560 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49656 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 49752 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49816 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 49912 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 50008 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 50072 + }, + { + "buffer" : 0, + "byteLength" : 96, + "byteOffset" : 50168 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 50264 + } + ], + "buffers" : [ + { + "byteLength" : 50328, + "uri" : "ClearCoatTest.bin" + } + ] +} diff --git a/test/models/glTF2/ClearCoat-glTF/PartialCoating.png b/test/models/glTF2/ClearCoat-glTF/PartialCoating.png new file mode 100644 index 0000000000000000000000000000000000000000..d579bbebb8a423eedb16594a7ef715fe7521a752 GIT binary patch literal 5077 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 Q@JSA$qoJ>ky=fQyKcnMWr2qf` literal 0 HcmV?d00001 diff --git a/test/models/glTF2/ClearCoat-glTF/PartialCoating_Alpha.png b/test/models/glTF2/ClearCoat-glTF/PartialCoating_Alpha.png new file mode 100644 index 0000000000000000000000000000000000000000..b1911d9f0a303a57181d44856f3f31ea18c11063 GIT binary patch literal 5065 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 Q_!|ylXYGipvh+*(4_Os-!2kdN literal 0 HcmV?d00001 diff --git a/test/models/glTF2/ClearCoat-glTF/PlasticWrap_normals.jpg b/test/models/glTF2/ClearCoat-glTF/PlasticWrap_normals.jpg new file mode 100644 index 0000000000000000000000000000000000000000..73e6260ef7126ccb11113179c58cf6611d3f3e9d GIT binary patch literal 144210 zcmbTddpy&9{QtkM4mwFHL}gd0Tv3)p6n2#džOVOJDoNkR^>T_xnWONE@;Rbq*4 zIn0@rb2*>3a?BiOIgBl{!+XD%>+?JP@%`ib`wh3fUNvRhT{&|%F#j%c0KH#lWzblU9v1#^pwmR5Gx>>be89Wm}V zZ+Up$zT@TRAMh|RC^#hiX+&hyv*?)TFJ2}kr@Tr{dz!pljPw6`{4d9)2*&l> zx^-*U$*qoS&2NF=XRYG8_4{=<>^OTx?uPHq11BDD+;uMTO~L0)8hTe3l-wS4ZB{;b za`X^yb!h*M?0;`yPyT<6?EfCv|1++B*w(dcz~rq}gdt&#orEzV+B#P%5TW(|mBE@8 zS=JvGIDFd7SL`O?FBfPEX_wMR7@O`>;h^bu&`Yma)=B7aXdi*y(i>1J>Z1 zz1CTd`n94v@k@%hY{oRAvM!yOmqt`BfRxOCOs>p8Il6b?qs|=`>mHp9NH~CHPDbQ; z^oD7};Qa5Vbo$NjC2*@cyYwj$^JG%CMwAih&>(lyF~Q^4J92YNnA906$eL`kJ?7NQ}nL{3T+omFSO1Ea7EQ zTxSKZ_o0e>q!F98vd4Y}Z_XsjVCpFQd==<$452>{V<>~2^J#qB!-5X~DQ4yFd#eRL zee{YtiA-pEFG8mWeJ;wShuB0ZXK`FT*>#y$7;1}uAVP9Qo_nmJ9n&W9 z%!KlIG!>J9Ipm+Chwh$Od4VOo_sRCdsGROWTfQf}_nP$IDTD1KQ$tIoUujv&Au`yg z_2#Os@77RmP0}QEXm@!CKh-F{CTU7t6SNJ(xF_%PXyRhW>>q?OJe}z*9d#x>hDJp! zC*R&{li2yZPVQ*O%Fa&{l8X-unY5?d{?aG1!y~a2FX8dY4K#~gj8P*ITt6~X7};cb z$PY6$qSSpcnCjz<)DNArk@(FmO+d{zhKc+en0`%32(7!2T25EWd&|4auoC^mE|_}O zO;~+vk-_{siFsk#&v-s|0_ud^Fm+EuCW={JIL~gLW)oIaq!Zp7*?RnZAH@<+ipwsx z-u3<^4?ovFmsH^|PlOQlM0X_ne8@H8vX_B=BQZL9+Eot8Lr~{!!5Z~*R(XOPt7iq@ zXeg(h(Q};|Bv{^mA%oq@hIY1A-Pe1h$$URRlUR2b8{=d!cwv}Vc7+U9Uyd8j>gj^N zV8$FRT5q+iu@!XA;r8RJ;G4EC_w0f5Gvz61e`AsA6+!kBLfKtWTmx1~Y5dOTT4vz0vYu>_Lx!1B3foKu+rhynMZ%q} z1SUtDhCW@B!L}I3_y{@#l!3KS_`bhdAUZ^^{EN`}ye5WJ-!aj;NBXllz*qcOkiQ9~ z-|3Yl-OVzU6y%Z`Z^dA14)vs~z~e&>zc_r zt70#$&;vxQ!1umnQuh5ccv~KgSy_#WUCXB-R3#W(t5hyRMXY?-m8|&zE=-0~?vGD` zMaQ~^`-^Am6(+Jhg zP~0eXW#ZA@U(V`qK~l%`F%Y}jz4SuaR?`b6~~l7y`jt`!3FzW4P9%7`&Y-nERpCt&zbJ9|=GRiZ(OYB%peJ))q5GEEI3AM+2VT9(2^r%(Z>3wB0nS-s6MBo?zDb zFY-9mq6t?K#LfF#!gcP5PdV|1dUr>O#&{TZr)g-3b=%&C&kc!6af6AN0Uh{z6V~;pYpC zPcf_NZkKqzS*ZI?@E*1?SPBO63urDg7O z>xgW#FL()mE@@w;+~P8LGdP=V=!N{YK*+?#w@cW?|27eNawM^}zvPgTMLk*X7$&0N zcTpd5`_ch)fX`D2VISg`LsgeTWV?QlDV_UO2Gc?lg`s2iQC!!eJ>%xu?Rk3|5xpW? zC6dmL4@>5UKj^(Tx%rk*|8>orw)<7pUiBh(lrU|8_$wlyZq#tbEFA<-ABg;}UROk6)ZhT~SS>VDG9MhJlyrJh4{MWLdDWHNJb7i3Dpk

    8#fXvU&U@TnHe66M)}` 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≫PJyOL#2noO42?ak;#zU>=*qI;o{}k?8jYM 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�-h@x9GW3t>NHF#%4~dx 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 literal 0 HcmV?d00001 diff --git a/test/models/invalid/readme.txt b/test/models/invalid/readme.txt index cab740a84..6ad8b4380 100644 --- a/test/models/invalid/readme.txt +++ b/test/models/invalid/readme.txt @@ -4,9 +4,9 @@ GENERAL ********************************************************* -The files in this directory are invalid ... some of them are empty, +The files in this directory are invalid ... some of them are empty, others have invalid vertices or faces, others are prepared to make - assimp allocate a few hundreds gigs of memory ... most are + assimp allocate a few hundreds gigs of memory ... most are actually regression tests, i.e. there was once a bugfix that fixed the respective loaders. @@ -18,8 +18,8 @@ crash. FILES ********************************************************* -OutOfMemory.off - the number of faces is invalid. There won't be - enough memory so std::vector::reserve() will most likely fail. +OutOfMemory.off - the number of faces is invalid. There won't be + enough memory so std::vector::reserve() will most likely fail. The exception should be caught in Importer.cpp. empty. - These files are completely empty. The corresponding diff --git a/test/regression/README.txt b/test/regression/README.txt index 3e90a143b..a37da9255 100644 --- a/test/regression/README.txt +++ b/test/regression/README.txt @@ -8,7 +8,7 @@ against a regression database provided with assimp (db.zip). A few failures are totally fine (see sections 7+). You need to worry if a huge majority of all files in a particular format (or post-processing configuration) fails as this might be a sign of a recent regression in assimp's codebase or -gross incompatibility with your system or compiler. +gross incompatibility with your system or compiler. 2) What do I need? --------------------------------------------------------------------------------- @@ -53,8 +53,8 @@ Edit the reg_settings.py file and add the path to your repository to The regression database includes mini dumps of the aiScene data structure, i.e. the scene hierarchy plus the sizes of all data arrays MUST match. Floating-point data buffers, such as vertex positions are handled less strictly: min, max and -average values are stored with low precision. This takes hardware- or -compiler-specific differences in floating-point computations into account. +average values are stored with low precision. This takes hardware- or +compiler-specific differences in floating-point computations into account. Generally, almost all significant regressions will be detected while the number of false positives is relatively low. diff --git a/test/unit/AbstractImportExportBase.h b/test/unit/AbstractImportExportBase.h index 72530aedc..7651d2e52 100644 --- a/test/unit/AbstractImportExportBase.h +++ b/test/unit/AbstractImportExportBase.h @@ -67,7 +67,7 @@ bool AbstractImportExportBase::importerTest() { return true; } -inline +inline bool AbstractImportExportBase::exporterTest() { return true; } diff --git a/test/unit/Common/utStandardShapes.cpp b/test/unit/Common/utStandardShapes.cpp index a5df5d898..e1bb6eef9 100644 --- a/test/unit/Common/utStandardShapes.cpp +++ b/test/unit/Common/utStandardShapes.cpp @@ -51,7 +51,7 @@ TEST_F( utStandardShapes, testMakeMesh ) { // The mNumIndices member of the second face is now incorrect const auto& face = aiMeshPtr->mFaces[0]; - EXPECT_EQ(face.mNumIndices, numIndicesPerPrimitive); + EXPECT_EQ(face.mNumIndices, numIndicesPerPrimitive); delete aiMeshPtr; } diff --git a/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp b/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp index ff3b4930c..6fa92f950 100644 --- a/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp +++ b/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp @@ -73,7 +73,7 @@ public: "Bone_3" | "" <----+ "Bone_2" | - "Bone_5" | + "Bone_5" | "" <----+ "" <----+ */ @@ -139,7 +139,7 @@ public: $body "Bodypart_1" <--+ | $body "Bodypart_2" | | $body "Bodypart1" | | - $body "Bodypart" ---|--+ + $body "Bodypart" ---|--+ $body "Bodypart_1" ---+ | $body "Bodypart2" | $body "Bodypart" ------+ diff --git a/test/unit/ImportExport/utAssjsonImportExport.cpp b/test/unit/ImportExport/utAssjsonImportExport.cpp index 7987804a9..13724f755 100644 --- a/test/unit/ImportExport/utAssjsonImportExport.cpp +++ b/test/unit/ImportExport/utAssjsonImportExport.cpp @@ -58,7 +58,18 @@ public: Exporter exporter; aiReturn res = exporter.Export(scene, "assjson", "./spider_test.json"); - return aiReturn_SUCCESS == res; + if (aiReturn_SUCCESS != res) { + return false; + } + + Assimp::ExportProperties exportProperties; + exportProperties.SetPropertyBool("JSON_SKIP_WHITESPACES", true); + aiReturn resNoWhitespace = exporter.Export(scene, "assjson", "./spider_test_nowhitespace.json", 0u, &exportProperties); + if (aiReturn_SUCCESS != resNoWhitespace) { + return false; + } + + return true; } }; diff --git a/test/unit/RandomNumberGeneration.h b/test/unit/RandomNumberGeneration.h index 81fcfb59c..892e78c06 100644 --- a/test/unit/RandomNumberGeneration.h +++ b/test/unit/RandomNumberGeneration.h @@ -53,11 +53,11 @@ class RandomUniformRealGenerator { public: RandomUniformRealGenerator() : dist_(), - rd_(), + rd_(), re_(rd_()) { // empty } - + RandomUniformRealGenerator(T min, T max) : dist_(min, max), rd_(), diff --git a/test/unit/SceneDiffer.cpp b/test/unit/SceneDiffer.cpp index 6ea28671a..368589bf3 100644 --- a/test/unit/SceneDiffer.cpp +++ b/test/unit/SceneDiffer.cpp @@ -48,7 +48,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { -SceneDiffer::SceneDiffer() +SceneDiffer::SceneDiffer() : m_diffs() { // empty } diff --git a/test/unit/SceneDiffer.h b/test/unit/SceneDiffer.h index 9e04c7210..e0dc6005a 100644 --- a/test/unit/SceneDiffer.h +++ b/test/unit/SceneDiffer.h @@ -72,4 +72,4 @@ private: std::vector m_diffs; }; -} +} diff --git a/test/unit/TestIOSystem.h b/test/unit/TestIOSystem.h index fdc3cc49b..4a42b23f0 100644 --- a/test/unit/TestIOSystem.h +++ b/test/unit/TestIOSystem.h @@ -61,7 +61,7 @@ public: virtual ~TestIOSystem() { // empty } - + virtual bool Exists( const char* ) const { return true; } diff --git a/test/unit/utColladaImportExport.cpp b/test/unit/utColladaImportExport.cpp index d5d81e396..76a39336e 100644 --- a/test/unit/utColladaImportExport.cpp +++ b/test/unit/utColladaImportExport.cpp @@ -382,3 +382,25 @@ public: TEST_F(utColladaZaeImportExport, importBlenFromFileTest) { EXPECT_TRUE(importerTest()); } + +TEST_F(utColladaZaeImportExport, importMakeHumanTest) { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/human.zae", aiProcess_ValidateDataStructure); + ASSERT_NE(nullptr, scene); + + // Expected number of items + EXPECT_EQ(scene->mNumMeshes, 2u); + EXPECT_EQ(scene->mNumMaterials, 2u); + EXPECT_EQ(scene->mNumAnimations, 0u); + EXPECT_EQ(scene->mNumTextures, 2u); + EXPECT_EQ(scene->mNumLights, 0u); + EXPECT_EQ(scene->mNumCameras, 0u); + + // Expected common metadata + aiString value; + EXPECT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_FORMAT, value)) << "No importer format metadata"; + EXPECT_STREQ("Collada Importer", value.C_Str()); + + EXPECT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_FORMAT_VERSION, value)) << "No format version metadata"; + EXPECT_STREQ("1.4.1", value.C_Str()); +} diff --git a/test/unit/utDefaultIOStream.cpp b/test/unit/utDefaultIOStream.cpp index 800fddbc9..d3e2c8a7e 100644 --- a/test/unit/utDefaultIOStream.cpp +++ b/test/unit/utDefaultIOStream.cpp @@ -66,7 +66,7 @@ TEST_F( utDefaultIOStream, FileSizeTest ) { { auto written = std::fwrite(data, sizeof(*data), dataCount, fs ); EXPECT_NE( 0U, written ); - + auto vflush = std::fflush( fs ); ASSERT_EQ(vflush, 0); diff --git a/test/unit/utFBXImporterExporter.cpp b/test/unit/utFBXImporterExporter.cpp index c78d56b97..4cfc9b152 100644 --- a/test/unit/utFBXImporterExporter.cpp +++ b/test/unit/utFBXImporterExporter.cpp @@ -365,7 +365,7 @@ TEST_F(utFBXImporterExporter, importMaxPbrMaterialsMetalRoughness) { float bumpMapAmt; // Presumably amount. ASSERT_EQ(mat->Get("$raw.3dsMax|main|bump_map_amt", aiTextureType_NONE, 0, bumpMapAmt), aiReturn_SUCCESS); EXPECT_EQ(bumpMapAmt, 0.75f); - + aiColor4D emitColor; ASSERT_EQ(mat->Get("$raw.3dsMax|main|emit_color", aiTextureType_NONE, 0, emitColor), aiReturn_SUCCESS); EXPECT_EQ(emitColor, aiColor4D(1, 1, 0, 1)); @@ -418,7 +418,7 @@ TEST_F(utFBXImporterExporter, importMaxPbrMaterialsSpecularGloss) { float bumpMapAmt; // Presumably amount. ASSERT_EQ(mat->Get("$raw.3dsMax|main|bump_map_amt", aiTextureType_NONE, 0, bumpMapAmt), aiReturn_SUCCESS); EXPECT_EQ(bumpMapAmt, 0.66f); - + aiColor4D emitColor; ASSERT_EQ(mat->Get("$raw.3dsMax|main|emit_color", aiTextureType_NONE, 0, emitColor), aiReturn_SUCCESS); EXPECT_EQ(emitColor, aiColor4D(1, 0, 1, 1)); diff --git a/test/unit/utFindDegenerates.cpp b/test/unit/utFindDegenerates.cpp index 1f8e8e93f..6f2abebfb 100644 --- a/test/unit/utFindDegenerates.cpp +++ b/test/unit/utFindDegenerates.cpp @@ -199,10 +199,10 @@ TEST_F(FindDegeneratesProcessTest, meshRemoval) { scene->mRootNode->mMeshes[3] = 3; scene->mRootNode->mMeshes[4] = 4; - mProcess->Execute(scene.get()); + mProcess->Execute(scene.get()); EXPECT_EQ(scene->mNumMeshes, 1u); EXPECT_EQ(scene->mMeshes[0], meshWhichSurvives); EXPECT_EQ(scene->mRootNode->mNumMeshes, 1u); - EXPECT_EQ(scene->mRootNode->mMeshes[0], 0u); + EXPECT_EQ(scene->mRootNode->mMeshes[0], 0u); } diff --git a/test/unit/utIOStreamBuffer.cpp b/test/unit/utIOStreamBuffer.cpp index 6d0d6a7d7..a0e4660df 100644 --- a/test/unit/utIOStreamBuffer.cpp +++ b/test/unit/utIOStreamBuffer.cpp @@ -81,14 +81,14 @@ TEST_F( IOStreamBufferTest, open_close_Test ) { EXPECT_FALSE( myBuffer.open( nullptr ) ); EXPECT_FALSE( myBuffer.close() ); - + const auto dataSize = sizeof(data); const auto dataCount = dataSize / sizeof(*data); char fname[]={ "octest.XXXXXX" }; auto* fs = MakeTmpFile(fname); ASSERT_NE(nullptr, fs); - + auto written = std::fwrite( data, sizeof(*data), dataCount, fs ); EXPECT_NE( 0U, written ); auto flushResult = std::fflush( fs ); @@ -107,7 +107,7 @@ TEST_F( IOStreamBufferTest, open_close_Test ) { } TEST_F( IOStreamBufferTest, readlineTest ) { - + const auto dataSize = sizeof(data); const auto dataCount = dataSize / sizeof(*data); diff --git a/test/unit/utIOSystem.cpp b/test/unit/utIOSystem.cpp index 767984deb..1e866515e 100644 --- a/test/unit/utIOSystem.cpp +++ b/test/unit/utIOSystem.cpp @@ -50,12 +50,12 @@ using namespace Assimp; class IOSystemTest : public ::testing::Test { public: - virtual void SetUp() { - pImp = new TestIOSystem(); + virtual void SetUp() { + pImp = new TestIOSystem(); } - - virtual void TearDown() { - delete pImp; + + virtual void TearDown() { + delete pImp; } protected: diff --git a/test/unit/utIssues.cpp b/test/unit/utIssues.cpp index cb1adb22c..5eeed6ad8 100644 --- a/test/unit/utIssues.cpp +++ b/test/unit/utIssues.cpp @@ -62,7 +62,7 @@ TEST_F( utIssues, OpacityBugWhenExporting_727 ) { aiScene *scene( TestModelFacttory::createDefaultTestModel( opacity ) ); Assimp::Importer importer; Assimp::Exporter exporter; - + std::string path = "dae"; const aiExportFormatDesc *desc = exporter.GetExportFormatDescription( 0 ); EXPECT_NE( desc, nullptr ); diff --git a/test/unit/utTypes.cpp b/test/unit/utTypes.cpp index cc354eb3d..1ac9a1d5e 100644 --- a/test/unit/utTypes.cpp +++ b/test/unit/utTypes.cpp @@ -53,8 +53,8 @@ class utTypes : public ::testing::Test { TEST_F( utTypes, Color3dCpmpareOpTest ) { aiColor3D col1( 1, 2, 3 ); aiColor3D col2( 4, 5, 6 ); - aiColor3D col3( col1 ); - + const aiColor3D &col3(col1); + EXPECT_FALSE( col1 == col2 ); EXPECT_FALSE( col2 == col3 ); EXPECT_TRUE( col1 == col3 ); diff --git a/test/unit/utVersion.cpp b/test/unit/utVersion.cpp index 0189cd2a9..0de6ef39c 100644 --- a/test/unit/utVersion.cpp +++ b/test/unit/utVersion.cpp @@ -55,7 +55,7 @@ TEST_F( utVersion, aiGetLegalStringTest ) { TEST_F( utVersion, aiGetVersionMinorTest ) { EXPECT_EQ( aiGetVersionMinor(), 0U ); } - + TEST_F( utVersion, aiGetVersionMajorTest ) { EXPECT_EQ( aiGetVersionMajor(), 5U ); } diff --git a/test/unit/utglTF2ImportExport.cpp b/test/unit/utglTF2ImportExport.cpp index 4110edcfc..e29d09145 100644 --- a/test/unit/utglTF2ImportExport.cpp +++ b/test/unit/utglTF2ImportExport.cpp @@ -57,10 +57,9 @@ using namespace Assimp; class utglTF2ImportExport : public AbstractImportExportBase { public: - virtual bool importerTest() { + virtual bool importerMatTest(const char *file, bool spec_gloss, std::array exp_modes = { aiTextureMapMode_Wrap, aiTextureMapMode_Wrap }) { Assimp::Importer importer; - const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured.gltf", - aiProcess_ValidateDataStructure); + const aiScene *scene = importer.ReadFile(file, aiProcess_ValidateDataStructure); EXPECT_NE(scene, nullptr); if (!scene) { return false; @@ -72,13 +71,49 @@ public: } const aiMaterial *material = scene->mMaterials[0]; + // This Material should be a PBR + aiShadingMode shadingMode; + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_SHADING_MODEL, shadingMode)); + EXPECT_EQ(aiShadingMode_PBR_BRDF, shadingMode); + + // Should import the texture as diffuse and as base color aiString path; - aiTextureMapMode modes[2]; + std::array modes; EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(aiTextureType_DIFFUSE, 0, &path, nullptr, nullptr, - nullptr, nullptr, modes)); + nullptr, nullptr, modes.data())); EXPECT_STREQ(path.C_Str(), "CesiumLogoFlat.png"); - EXPECT_EQ(modes[0], aiTextureMapMode_Mirror); - EXPECT_EQ(modes[1], aiTextureMapMode_Clamp); + EXPECT_EQ(exp_modes, modes); + + // Also as Base Color + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(aiTextureType_BASE_COLOR, 0, &path, nullptr, nullptr, + nullptr, nullptr, modes.data())); + EXPECT_STREQ(path.C_Str(), "CesiumLogoFlat.png"); + EXPECT_EQ(exp_modes, modes); + + // Should have a MetallicFactor (default is 1.0) + ai_real metal_factor = ai_real(0.5); + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_METALLIC_FACTOR, metal_factor)); + EXPECT_EQ(ai_real(0.0), metal_factor); + + // And a roughness factor (default is 1.0) + ai_real roughness_factor = ai_real(0.5); + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_ROUGHNESS_FACTOR, roughness_factor)); + EXPECT_EQ(ai_real(1.0), roughness_factor); + + aiColor3D spec_color = { 0, 0, 0 }; + ai_real glossiness = ai_real(0.5); + if (spec_gloss) { + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_COLOR_SPECULAR, spec_color)); + constexpr ai_real spec_val(0.20000000298023225); // From the file + EXPECT_EQ(spec_val, spec_color.r); + EXPECT_EQ(spec_val, spec_color.g); + EXPECT_EQ(spec_val, spec_color.b); + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_GLOSSINESS_FACTOR, glossiness)); + EXPECT_EQ(ai_real(1.0), glossiness); + } else { + EXPECT_EQ(aiReturn_FAILURE, material->Get(AI_MATKEY_COLOR_SPECULAR, spec_color)); + EXPECT_EQ(aiReturn_FAILURE, material->Get(AI_MATKEY_GLOSSINESS_FACTOR, glossiness)); + } return true; } @@ -105,14 +140,89 @@ public: }; TEST_F(utglTF2ImportExport, importglTF2FromFileTest) { - EXPECT_TRUE(importerTest()); + EXPECT_TRUE(importerMatTest(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured.gltf", false, {aiTextureMapMode_Mirror, aiTextureMapMode_Clamp})); } TEST_F(utglTF2ImportExport, importBinaryglTF2FromFileTest) { EXPECT_TRUE(binaryImporterTest()); } +TEST_F(utglTF2ImportExport, importglTF2_KHR_materials_pbrSpecularGlossiness) { + EXPECT_TRUE(importerMatTest(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured.gltf", true)); +} + +void VerifyClearCoatScene(const aiScene *scene) { + ASSERT_NE(nullptr, scene); + + ASSERT_TRUE(scene->HasMaterials()); + + // Find a specific Clearcoat material and check the values + const aiString partial_coated("Partial_Coated"); + bool found_partial_coat = false; + for (size_t i = 0; i < scene->mNumMaterials; ++i) { + const aiMaterial *material = scene->mMaterials[i]; + ASSERT_NE(nullptr, material); + if (material->GetName() == partial_coated) { + found_partial_coat = true; + + ai_real clearcoat_factor(0.0f); + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_CLEARCOAT_FACTOR, clearcoat_factor)); + EXPECT_EQ(ai_real(1.0f), clearcoat_factor); + + ai_real clearcoat_rough_factor(0.0f); + EXPECT_EQ(aiReturn_SUCCESS, material->Get(AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR, clearcoat_rough_factor)); + EXPECT_EQ(ai_real(0.03f), clearcoat_rough_factor); + + // Should import the texture as diffuse and as base color + aiString path; + std::array modes; + static const std::array exp_modes = { aiTextureMapMode_Wrap, aiTextureMapMode_Wrap }; + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(AI_MATKEY_CLEARCOAT_TEXTURE, &path, nullptr, nullptr, + nullptr, nullptr, modes.data())); + EXPECT_STREQ(path.C_Str(), "PartialCoating.png"); + EXPECT_EQ(exp_modes, modes); + } + } + EXPECT_TRUE(found_partial_coat); +} + +TEST_F(utglTF2ImportExport, importglTF2_KHR_materials_clearcoat) { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/ClearCoat-glTF/ClearCoatTest.gltf", aiProcess_ValidateDataStructure); + VerifyClearCoatScene(scene); +} + #ifndef ASSIMP_BUILD_NO_EXPORT + +TEST_F(utglTF2ImportExport, importglTF2AndExport_KHR_materials_clearcoat) { + { + Assimp::Importer importer; + Assimp::Exporter exporter; + const aiScene* scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/ClearCoat-glTF/ClearCoatTest.gltf", aiProcess_ValidateDataStructure); + ASSERT_NE(nullptr, scene); + // Export + EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "glb2", ASSIMP_TEST_MODELS_DIR "/glTF2/ClearCoat-glTF/ClearCoatTest_out.glb")); + } + + // And re-import + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/ClearCoat-glTF/ClearCoatTest_out.glb", aiProcess_ValidateDataStructure); + VerifyClearCoatScene(scene); +} + +TEST_F(utglTF2ImportExport, importglTF2AndExport_KHR_materials_pbrSpecularGlossiness) { + Assimp::Importer importer; + Assimp::Exporter exporter; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured.gltf", + aiProcess_ValidateDataStructure); + EXPECT_NE(nullptr, scene); + // Export + EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "glb2", ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured_out.glb")); + + // And re-import + EXPECT_TRUE(importerMatTest(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-pbrSpecularGlossiness/BoxTextured_out.glb", true)); +} + TEST_F(utglTF2ImportExport, importglTF2AndExportToOBJ) { Assimp::Importer importer; Assimp::Exporter exporter; @@ -130,6 +240,7 @@ TEST_F(utglTF2ImportExport, importglTF2EmbeddedAndExportToOBJ) { EXPECT_NE(nullptr, scene); EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "obj", ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF-Embedded/BoxTextured_out.obj")); } + #endif // ASSIMP_BUILD_NO_EXPORT TEST_F(utglTF2ImportExport, importglTF2PrimitiveModePointsWithoutIndices) { @@ -492,32 +603,58 @@ TEST_F(utglTF2ImportExport, sceneMetadata) { } TEST_F(utglTF2ImportExport, texcoords) { + Assimp::Importer importer; - const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTexcoords-glTF/boxTexcoords.gltf", - aiProcess_ValidateDataStructure); + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTexcoords-glTF/boxTexcoords.gltf", aiProcess_ValidateDataStructure); + ASSERT_NE(scene, nullptr); + ASSERT_TRUE(scene->HasMaterials()); + const aiMaterial *material = scene->mMaterials[0]; + + aiString path; + unsigned int uvIndex = 255; + aiTextureMapMode modes[2]; + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(AI_MATKEY_BASE_COLOR_TEXTURE, &path, nullptr, &uvIndex, nullptr, nullptr, modes)); + EXPECT_STREQ(path.C_Str(), "texture.png"); + EXPECT_EQ(uvIndex, 0u); + + uvIndex = 255; + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, &path, nullptr, &uvIndex, nullptr, nullptr, modes)); + EXPECT_STREQ(path.C_Str(), "texture.png"); + EXPECT_EQ(uvIndex, 1u); +} + +#ifndef ASSIMP_BUILD_NO_EXPORT + +TEST_F(utglTF2ImportExport, texcoords_export) { + { + Assimp::Importer importer; + Assimp::Exporter exporter; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTexcoords-glTF/boxTexcoords.gltf", aiProcess_ValidateDataStructure); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "glb2", ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTexcoords-glTF/boxTexcoords.gltf_out.glb")); + } + + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTexcoords-glTF/boxTexcoords.gltf", aiProcess_ValidateDataStructure); ASSERT_NE(scene, nullptr); ASSERT_TRUE(scene->HasMaterials()); const aiMaterial *material = scene->mMaterials[0]; aiString path; + unsigned int uvIndex = 255; aiTextureMapMode modes[2]; - EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(aiTextureType_DIFFUSE, 0, &path, nullptr, nullptr, - nullptr, nullptr, modes)); + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(AI_MATKEY_BASE_COLOR_TEXTURE, &path, nullptr, &uvIndex, nullptr, nullptr, modes)); EXPECT_STREQ(path.C_Str(), "texture.png"); + EXPECT_EQ(uvIndex, 0u); - int uvIndex = -1; - EXPECT_EQ(aiGetMaterialInteger(material, AI_MATKEY_GLTF_TEXTURE_TEXCOORD(aiTextureType_DIFFUSE, 0), &uvIndex), aiReturn_SUCCESS); - EXPECT_EQ(uvIndex, 0); - - // Using manual macro expansion of AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE here. - // The following works with some but not all compilers: - // #define APPLY(X, Y) X(Y) - // ..., APPLY(AI_MATKEY_GLTF_TEXTURE_TEXCOORD, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE), ... - EXPECT_EQ(aiGetMaterialInteger(material, AI_MATKEY_GLTF_TEXTURE_TEXCOORD(aiTextureType_UNKNOWN, 0), &uvIndex), aiReturn_SUCCESS); - EXPECT_EQ(uvIndex, 1); + uvIndex = 255; + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, &path, nullptr, &uvIndex, nullptr, nullptr, modes)); + EXPECT_STREQ(path.C_Str(), "texture.png"); + EXPECT_EQ(uvIndex, 1u); } +#endif // ASSIMP_BUILD_NO_EXPORT TEST_F(utglTF2ImportExport, recursive_nodes) { Assimp::Importer importer; const aiScene* scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/RecursiveNodes/RecursiveNodes.gltf", aiProcess_ValidateDataStructure); @@ -555,7 +692,7 @@ TEST_F(utglTF2ImportExport, indexOutOfRange) { } }; LogObserver logObserver; - + DefaultLogger::get()->attachStream(&logObserver); const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/IndexOutOfRange/IndexOutOfRange.gltf", aiProcess_ValidateDataStructure); ASSERT_NE(scene, nullptr); @@ -611,7 +748,8 @@ TEST_F(utglTF2ImportExport, import_dracoEncoded) { TEST_F(utglTF2ImportExport, wrongTypes) { // Deliberately broken version of the BoxTextured.gltf asset. - std::vector> wrongTypes = { + using tup_T = std::tuple; + std::vector wrongTypes = { { "/glTF2/wrongTypes/badArray.gltf", "array", "primitives", "meshes[0]" }, { "/glTF2/wrongTypes/badString.gltf", "string", "name", "scenes[0]" }, { "/glTF2/wrongTypes/badUint.gltf", "uint", "index", "materials[0]" }, diff --git a/tools/assimp_cmd/CMakeLists.txt b/tools/assimp_cmd/CMakeLists.txt index 3a39fa748..5aeac0f7b 100644 --- a/tools/assimp_cmd/CMakeLists.txt +++ b/tools/assimp_cmd/CMakeLists.txt @@ -1,6 +1,6 @@ # Open Asset Import Library (assimp) # ---------------------------------------------------------------------- -# +# # Copyright (c) 2006-2021, assimp team diff --git a/tools/assimp_cmd/Export.cpp b/tools/assimp_cmd/Export.cpp index 1e2f10541..6c3c41de9 100644 --- a/tools/assimp_cmd/Export.cpp +++ b/tools/assimp_cmd/Export.cpp @@ -7,8 +7,8 @@ Copyright (c) 2006-2021, 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 +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 @@ -25,16 +25,16 @@ conditions are met: 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 +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 +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 +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 +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. --------------------------------------------------------------------------- */ diff --git a/tools/assimp_cmd/ImageExtractor.cpp b/tools/assimp_cmd/ImageExtractor.cpp index 105c4fe37..23aa9c249 100644 --- a/tools/assimp_cmd/ImageExtractor.cpp +++ b/tools/assimp_cmd/ImageExtractor.cpp @@ -7,8 +7,8 @@ Copyright (c) 2006-2021, 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 +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 @@ -25,16 +25,16 @@ conditions are met: 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 +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 +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 +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 +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. --------------------------------------------------------------------------- */ @@ -356,6 +356,6 @@ int Assimp_Extract(const char *const *params, unsigned int num) { return m; } } - + return AssimpCmdError::Success; } diff --git a/tools/assimp_cmd/Info.cpp b/tools/assimp_cmd/Info.cpp index e0d511a73..2c35ba227 100644 --- a/tools/assimp_cmd/Info.cpp +++ b/tools/assimp_cmd/Info.cpp @@ -316,7 +316,7 @@ int Assimp_Info (const char* const* params, unsigned int num) { printf("assimp info: Invalid arguments, verbose and silent at the same time are forbitten. "); return AssimpCmdInfoError::InvalidCombinaisonOfArguments; } - + // Parse post-processing flags unless -r was specified ImportData import; if (!raw) { diff --git a/tools/assimp_cmd/Main.cpp b/tools/assimp_cmd/Main.cpp index 2fb7559bb..8d76e1f5e 100644 --- a/tools/assimp_cmd/Main.cpp +++ b/tools/assimp_cmd/Main.cpp @@ -9,8 +9,8 @@ Copyright (c) 2006-2021, 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 +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 @@ -27,16 +27,16 @@ conditions are met: 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 +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 +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 +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 +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. --------------------------------------------------------------------------- */ @@ -47,7 +47,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "Main.h" -const char* AICMD_MSG_ABOUT = +const char* AICMD_MSG_ABOUT = "------------------------------------------------------ \n" "Open Asset Import Library (\"Assimp\", https://github.com/assimp/assimp) \n" " -- Commandline toolchain --\n" @@ -55,7 +55,7 @@ const char* AICMD_MSG_ABOUT = "Version %i.%i %s%s%s%s%s(GIT commit %x)\n\n"; -const char* AICMD_MSG_HELP = +const char* AICMD_MSG_HELP = "assimp \n\n" " verbs:\n" " \tinfo - Quick file stats\n" @@ -106,7 +106,7 @@ int main (int argc, char* argv[]) } // assimp help - // Display some basic help (--help and -h work as well + // Display some basic help (--help and -h work as well // because people could try them intuitively) if (!strcmp(argv[1], "help") || !strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")) { printf("%s",AICMD_MSG_HELP); @@ -114,7 +114,7 @@ int main (int argc, char* argv[]) } // assimp cmpdump - // Compare two mini model dumps (regression suite) + // Compare two mini model dumps (regression suite) if (! strcmp(argv[1], "cmpdump")) { return Assimp_CompareDump (&argv[2],argc-2); } @@ -125,7 +125,7 @@ int main (int argc, char* argv[]) globalImporter = &imp; #ifndef ASSIMP_BUILD_NO_EXPORT - // + // Assimp::Exporter exp; globalExporter = &exp; #endif @@ -145,7 +145,7 @@ int main (int argc, char* argv[]) // List all export file formats supported by Assimp (not the file extensions, just the format identifiers!) if (! strcmp(argv[1], "listexport")) { aiString s; - + for(size_t i = 0, end = exp.GetExportFormatCount(); i < end; ++i) { const aiExportFormatDesc* const e = exp.GetExportFormatDescription(i); s.Append( e->id ); @@ -176,7 +176,7 @@ int main (int argc, char* argv[]) return AssimpCmdError::Success; } } - + printf("Unknown file format id: \'%s\'\n",argv[2]); return AssimpCmdError::UnknownFileFormat; } @@ -207,13 +207,13 @@ int main (int argc, char* argv[]) return Assimp_Info ((const char**)&argv[2],argc-2); } - // assimp dump - // Dump a model to a file + // assimp dump + // Dump a model to a file if (! strcmp(argv[1], "dump")) { return Assimp_Dump (&argv[2],argc-2); } - // assimp extract + // assimp extract // Extract an embedded texture from a file if (! strcmp(argv[1], "extract")) { return Assimp_Extract (&argv[2],argc-2); @@ -236,7 +236,7 @@ int main (int argc, char* argv[]) void SetLogStreams(const ImportData& imp) { printf("\nAttaching log stream ... OK\n"); - + unsigned int flags = 0; if (imp.logFile.length()) { flags |= aiDefaultLogStream_FILE; @@ -264,7 +264,7 @@ void PrintHorBar() // ------------------------------------------------------------------------------ // Import a specific file const aiScene* ImportModel( - const ImportData& imp, + const ImportData& imp, const std::string& path) { // Attach log streams @@ -282,7 +282,7 @@ const aiScene* ImportModel( if (imp.showLog) { PrintHorBar(); } - + // do the actual import, measure time const clock_t first = clock(); @@ -302,7 +302,7 @@ const aiScene* ImportModel( printf("Importing file ... OK \n import took approx. %.5f seconds\n" "\n",seconds); - if (imp.log) { + if (imp.log) { FreeLogStreams(); } return scene; @@ -310,8 +310,8 @@ const aiScene* ImportModel( #ifndef ASSIMP_BUILD_NO_EXPORT // ------------------------------------------------------------------------------ -bool ExportModel(const aiScene* pOut, - const ImportData& imp, +bool ExportModel(const aiScene* pOut, + const ImportData& imp, const std::string& path, const char* pID) { @@ -352,7 +352,7 @@ bool ExportModel(const aiScene* pOut, printf("Exporting file ... OK \n export took approx. %.5f seconds\n" "\n",seconds); - if (imp.log) { + if (imp.log) { FreeLogStreams(); } @@ -363,7 +363,7 @@ bool ExportModel(const aiScene* pOut, // ------------------------------------------------------------------------------ // Process standard arguments int ProcessStandardArguments( - ImportData& fill, + ImportData& fill, const char* const * params, unsigned int num) { @@ -396,7 +396,7 @@ int ProcessStandardArguments( // // -c --config-file= - for (unsigned int i = 0; i < num;++i) + for (unsigned int i = 0; i < num;++i) { const char *param = params[ i ]; printf( "param = %s\n", param ); @@ -504,11 +504,11 @@ int ProcessStandardArguments( else if (!strncmp(params[i], "-rx=", 4) || !strncmp(params[i], "--rotation-x=", 13)) { std::string value = std::string(params[i] + (params[i][1] == '-' ? 13 : 4)); fill.rot.x = std::stof(value); - } + } else if (!strncmp(params[i], "-ry=", 4) || !strncmp(params[i], "--rotation-y=", 13)) { std::string value = std::string(params[i] + (params[i][1] == '-' ? 13 : 4)); fill.rot.y = std::stof(value); - } + } else if (!strncmp(params[i], "-rz=", 4) || !strncmp(params[i], "--rotation-z=", 13)) { std::string value = std::string(params[i] + (params[i][1] == '-' ? 13 : 4)); fill.rot.z = std::stof(value); @@ -530,7 +530,7 @@ int ProcessStandardArguments( // ------------------------------------------------------------------------------ int Assimp_TestBatchLoad ( - const char* const* params, + const char* const* params, unsigned int num) { for(unsigned int i = 0; i < num; ++i) { diff --git a/tools/assimp_cmd/Main.h b/tools/assimp_cmd/Main.h index e7fbb6c75..5ac306abd 100644 --- a/tools/assimp_cmd/Main.h +++ b/tools/assimp_cmd/Main.h @@ -7,8 +7,8 @@ Copyright (c) 2006-2021, 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 +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 @@ -25,16 +25,16 @@ conditions are met: 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 +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 +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 +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 +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. --------------------------------------------------------------------------- */ @@ -144,7 +144,7 @@ enum AssimpCmdError { * @param params Command line parameters to be processed * @param num NUmber of params * @return An #AssimpCmdError value. */ -int ProcessStandardArguments(ImportData& fill, +int ProcessStandardArguments(ImportData& fill, const char* const* params, unsigned int num); @@ -153,7 +153,7 @@ int ProcessStandardArguments(ImportData& fill, * @param imp Import configuration to be used * @param path Path to the file to be read */ const aiScene* ImportModel( - const ImportData& imp, + const ImportData& imp, const std::string& path); #ifndef ASSIMP_BUILD_NO_EXPORT @@ -163,8 +163,8 @@ const aiScene* ImportModel( * @param imp Import configuration to be used * @param path Path to the file to be written * @param format Format id*/ -bool ExportModel(const aiScene* pOut, - const ImportData& imp, +bool ExportModel(const aiScene* pOut, + const ImportData& imp, const std::string& path, const char* pID); @@ -176,7 +176,7 @@ bool ExportModel(const aiScene* pOut, * @param Number of params * @return An #AssimpCmdError value.*/ int Assimp_Dump ( - const char* const* params, + const char* const* params, unsigned int num); /// \enum AssimpCmdExportError @@ -186,7 +186,7 @@ enum AssimpCmdExportError { FailedToExportModel, // Add new error codes here... - + LastAssimpCmdExportError, // Must be last. }; @@ -196,7 +196,7 @@ enum AssimpCmdExportError { * @param Number of params * @return Either an #AssimpCmdError or #AssimpCmdExportError value. */ int Assimp_Export ( - const char* const* params, + const char* const* params, unsigned int num); /// \enum AssimpCmdExtractError @@ -217,7 +217,7 @@ enum AssimpCmdExtractError { * @param Number of params * @return Either an #AssimpCmdError or #AssimpCmdExtractError value. */ int Assimp_Extract ( - const char* const* params, + const char* const* params, unsigned int num); /// \enum AssimpCmdCompareDumpError @@ -238,7 +238,7 @@ enum AssimpCmdCompareDumpError { * @param Number of params * @return Either an #AssimpCmdError or #AssimpCmdCompareDumpError. */ int Assimp_CompareDump ( - const char* const* params, + const char* const* params, unsigned int num); /// \enum AssimpCmdInfoError @@ -257,7 +257,7 @@ enum AssimpCmdInfoError { * @param Number of params * @return Either an #AssimpCmdError or #AssimpCmdInfoError value. */ int Assimp_Info ( - const char* const* params, + const char* const* params, unsigned int num); // ------------------------------------------------------------------------------ @@ -266,7 +266,7 @@ int Assimp_Info ( * @param Number of params * @return An #AssimpCmdError value. */ int Assimp_TestBatchLoad ( - const char* const* params, + const char* const* params, unsigned int num); diff --git a/tools/assimp_cmd/WriteDump.cpp b/tools/assimp_cmd/WriteDump.cpp index 5809d4ce6..fd8839a17 100644 --- a/tools/assimp_cmd/WriteDump.cpp +++ b/tools/assimp_cmd/WriteDump.cpp @@ -7,8 +7,8 @@ Copyright (c) 2006-2021, 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 +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 @@ -25,16 +25,16 @@ conditions are met: 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 +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 +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 +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 +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. --------------------------------------------------------------------------- */ diff --git a/tools/assimp_cmd/resource.h b/tools/assimp_cmd/resource.h index c516b5e5c..caf3a0a69 100644 --- a/tools/assimp_cmd/resource.h +++ b/tools/assimp_cmd/resource.h @@ -9,7 +9,7 @@ // Next default values for new objects -// +// #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NO_MFC 1 diff --git a/tools/assimp_view/AnimEvaluator.cpp b/tools/assimp_view/AnimEvaluator.cpp index df5167923..5a2ddc182 100644 --- a/tools/assimp_view/AnimEvaluator.cpp +++ b/tools/assimp_view/AnimEvaluator.cpp @@ -39,9 +39,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------- */ -#include "assimp_view.h" +#include "AnimEvaluator.h" -#include +#include +#include using namespace AssimpView; diff --git a/tools/assimp_view/AnimEvaluator.h b/tools/assimp_view/AnimEvaluator.h index 950763081..394ebef4a 100644 --- a/tools/assimp_view/AnimEvaluator.h +++ b/tools/assimp_view/AnimEvaluator.h @@ -1,4 +1,3 @@ -/** Calculates a pose for a given time of an animation */ /* --------------------------------------------------------------------------- Open Asset Import Library (assimp) @@ -40,15 +39,21 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------- */ +#pragma once #ifndef AV_ANIMEVALUATOR_H_INCLUDED #define AV_ANIMEVALUATOR_H_INCLUDED +/** Calculates a pose for a given time of an animation */ + #include #include +#include + +struct aiAnimation; namespace AssimpView { -/** +/** * @brief Calculates transformations for a given timestamp from a set of animation tracks. Not directly useful, * better use the AnimPlayer class. */ @@ -63,18 +68,19 @@ public: /// @brief The class destructor. ~AnimEvaluator(); - /** Evaluates the animation tracks for a given time stamp. The calculated pose can be retrieved as a - * array of transformation matrices afterwards by calling GetTransformations(). - * @param pTime The time for which you want to evaluate the animation, in seconds. Will be mapped into the animation cycle, so - * it can be an arbitrary value. Best use with ever-increasing time stamps. - */ + /// @brief Evaluates the animation tracks for a given time stamp. + /// The calculated pose can be retrieved as an array of transformation + /// matrices afterwards by calling GetTransformations(). + /// @param pTime The time for which you want to evaluate the animation, in seconds. + /// Will be mapped into the animation cycle, so it can get an arbitrary + /// value. Best use with ever-increasing time stamps. void Evaluate(double pTime); - /** Returns the transform matrices calculated at the last Evaluate() call. The array matches the mChannels array of - * the aiAnimation. */ + /// @brief Returns the transform matrices calculated at the last Evaluate() call. + /// The array matches the mChannels array of the aiAnimation. const std::vector &GetTransformations() const { return mTransforms; } -protected: +private: const aiAnimation *mAnim; double mLastTime; std::vector> mLastPositions; diff --git a/tools/assimp_view/CMakeLists.txt b/tools/assimp_view/CMakeLists.txt index 8ff556f05..0199392fe 100644 --- a/tools/assimp_view/CMakeLists.txt +++ b/tools/assimp_view/CMakeLists.txt @@ -1,6 +1,6 @@ # Open Asset Import Library (assimp) # ---------------------------------------------------------------------- -# +# # Copyright (c) 2006-2021, assimp team diff --git a/tools/assimp_view/Display.cpp b/tools/assimp_view/Display.cpp index 4178ab955..ac9aa5329 100644 --- a/tools/assimp_view/Display.cpp +++ b/tools/assimp_view/Display.cpp @@ -105,7 +105,7 @@ void GetNodeCount(aiNode* pcNode, unsigned int* piCnt) int CDisplay::EnableAnimTools(BOOL hm) { EnableWindow(GetDlgItem(g_hDlg,IDC_PLAY),hm); EnableWindow(GetDlgItem(g_hDlg,IDC_SLIDERANIM),hm); - + return 1; } @@ -171,7 +171,7 @@ int CDisplay::AddNodeToDisplayList( { iIndex += iDepth * 100; } - else + else iIndex += iDepth * 10; ai_snprintf(chTemp, MAXLEN,"Node %u",iIndex); } @@ -1053,7 +1053,7 @@ int CDisplay::OnSetupTextureView(TextureInfo* pcNew) case aiTextureOp_SmoothAdd: szOp = "addsmooth"; break; - default: + default: szOp = "mul"; break; }; diff --git a/tools/assimp_view/Material.cpp b/tools/assimp_view/Material.cpp index bcc93011e..100074445 100644 --- a/tools/assimp_view/Material.cpp +++ b/tools/assimp_view/Material.cpp @@ -325,9 +325,10 @@ int CMaterialManager::FindValidPath(aiString* p_szString) // first check whether we can directly load the file FILE* pFile = fopen(p_szString->data,"rb"); - if (pFile)fclose(pFile); - else - { + if (pFile) { + fclose(pFile); + } + else { // check whether we can use the directory of the asset as relative base char szTemp[MAX_PATH*2], tmp2[MAX_PATH*2]; strcpy(szTemp, g_szFileName); diff --git a/tools/assimp_view/MaterialManager.h b/tools/assimp_view/MaterialManager.h index 77ca4224f..9aeb5e992 100644 --- a/tools/assimp_view/MaterialManager.h +++ b/tools/assimp_view/MaterialManager.h @@ -52,24 +52,11 @@ namespace AssimpView { */ //------------------------------------------------------------------------------- class CMaterialManager { -private: friend class CDisplay; - // default constructor - CMaterialManager() : - m_iShaderCount(0), sDefaultTexture() {} - - ~CMaterialManager() { - if (sDefaultTexture) { - sDefaultTexture->Release(); - } - Reset(); - } - public: //------------------------------------------------------------------ // Singleton accessors - static CMaterialManager s_cInstance; inline static CMaterialManager &Instance() { return s_cInstance; } @@ -80,24 +67,20 @@ public: // Must be called before CreateMaterial() to prevent memory leaking void DeleteMaterial(AssetHelper::MeshHelper *pcIn); - //------------------------------------------------------------------ - // Create the material for a mesh. - // - // The function checks whether an identical shader is already in use. - // A shader is considered to be identical if it has the same input - // signature and takes the same number of texture channels. - int CreateMaterial(AssetHelper::MeshHelper *pcMesh, - const aiMesh *pcSource); - - //------------------------------------------------------------------ - // Setup the material for a given mesh - // pcMesh Mesh to be rendered - // pcProj Projection matrix - // aiMe Current world matrix - // pcCam Camera matrix - // vPos Position of the camera - // TODO: Extract camera position from matrix ... - // + /// @brief Create the material for a mesh. + /// + /// The function checks whether an identical shader is already in use. + /// A shader is considered to be identical if it has the same input + /// signature and takes the same number of texture channels. + int CreateMaterial(AssetHelper::MeshHelper *pcMesh, const aiMesh *pcSource); + + /// @brief Setup the material for a given mesh. + /// @param pcMesh Mesh to be rendered + /// @param pcProj Projection matrix + /// @param aiMe Current world matrix + /// @param pcCam Camera matrix + /// @param vPos Position of the camera + /// @return 0 if successful. int SetupMaterial(AssetHelper::MeshHelper *pcMesh, const aiMatrix4x4 &pcProj, const aiMatrix4x4 &aiMe, @@ -143,14 +126,29 @@ public: // Reset the state of the class // Called whenever a new asset is loaded inline void Reset() { - this->m_iShaderCount = 0; - for (TextureCache::iterator it = sCachedTextures.begin(); it != sCachedTextures.end(); ++it) { - (*it).second->Release(); + m_iShaderCount = 0; + for (auto & sCachedTexture : sCachedTextures) { + sCachedTexture.second->Release(); } sCachedTextures.clear(); } private: + // The default constructor + CMaterialManager() : + m_iShaderCount(0), + sDefaultTexture() { + // empty + } + + // Destructor, private. + ~CMaterialManager() { + if (sDefaultTexture) { + sDefaultTexture->Release(); + } + Reset(); + } + //------------------------------------------------------------------ // find a valid path to a texture file // @@ -183,15 +181,14 @@ private: bool HasAlphaPixels(IDirect3DTexture9 *piTexture); private: - // + static CMaterialManager s_cInstance; + // Specifies the number of different shaders generated for // the current asset. This number is incremented by CreateMaterial() // each time a shader isn't found in cache and needs to be created - // unsigned int m_iShaderCount; IDirect3DTexture9 *sDefaultTexture; - - typedef std::map TextureCache; + using TextureCache = std::map; TextureCache sCachedTextures; }; diff --git a/tools/assimp_view/MeshRenderer.cpp b/tools/assimp_view/MeshRenderer.cpp index 0ec12e5b1..bc1a5236f 100644 --- a/tools/assimp_view/MeshRenderer.cpp +++ b/tools/assimp_view/MeshRenderer.cpp @@ -61,11 +61,14 @@ int CMeshRenderer::DrawUnsorted(unsigned int iIndex) { D3DPRIMITIVETYPE type = D3DPT_POINTLIST; switch (g_pcAsset->pcScene->mMeshes[iIndex]->mPrimitiveTypes) { case aiPrimitiveType_POINT: - type = D3DPT_POINTLIST;break; + type = D3DPT_POINTLIST; + break; case aiPrimitiveType_LINE: - type = D3DPT_LINELIST;break; + type = D3DPT_LINELIST; + break; case aiPrimitiveType_TRIANGLE: - type = D3DPT_TRIANGLELIST;break; + type = D3DPT_TRIANGLELIST; + break; } // and draw the mesh g_piDevice->DrawIndexedPrimitive(type, diff --git a/tools/assimp_view/MessageProc.cpp b/tools/assimp_view/MessageProc.cpp index 580d35bf1..56831eb2a 100644 --- a/tools/assimp_view/MessageProc.cpp +++ b/tools/assimp_view/MessageProc.cpp @@ -2206,7 +2206,7 @@ int APIENTRY _tWinMain(HINSTANCE hInstance, "ASSIMP ModelViewer",MB_OK); return -4; } - + CLogDisplay::Instance().AddEntry("[OK] Here we go!"); // create the log window diff --git a/tools/assimp_view/Shaders.cpp b/tools/assimp_view/Shaders.cpp index a3404d5bf..b8ee8dbf8 100644 --- a/tools/assimp_view/Shaders.cpp +++ b/tools/assimp_view/Shaders.cpp @@ -7,8 +7,8 @@ Copyright (c) 2006-2021, 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 +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 @@ -25,16 +25,16 @@ conditions are met: 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 +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 +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 +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 +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. --------------------------------------------------------------------------- */ diff --git a/tools/assimp_view/assimp_view.cpp b/tools/assimp_view/assimp_view.cpp index 260b22941..e780e2aaf 100644 --- a/tools/assimp_view/assimp_view.cpp +++ b/tools/assimp_view/assimp_view.cpp @@ -7,8 +7,8 @@ Copyright (c) 2006-2021, 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 +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 @@ -25,16 +25,16 @@ 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 +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 +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 +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 +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. --------------------------------------------------------------------------- */ @@ -489,7 +489,7 @@ int CreateAssetData() { nidx = 3; break; default: - ai_assert(false); + CLogWindow::Instance().WriteLine("Unknown primitiv type"); break; }; @@ -500,8 +500,7 @@ int CreateAssetData() { // check whether we can use 16 bit indices if (numIndices >= 65536) { // create 32 bit index buffer - if (FAILED(g_piDevice->CreateIndexBuffer(4 * - numIndices, + if (FAILED(g_piDevice->CreateIndexBuffer(4 * numIndices, D3DUSAGE_WRITEONLY | dwUsage, D3DFMT_INDEX32, D3DPOOL_DEFAULT, @@ -523,7 +522,7 @@ int CreateAssetData() { } else { // create 16 bit index buffer if (FAILED(g_piDevice->CreateIndexBuffer(2 * - numIndices, +numIndices, D3DUSAGE_WRITEONLY | dwUsage, D3DFMT_INDEX16, D3DPOOL_DEFAULT, diff --git a/tools/assimp_view/resource.h b/tools/assimp_view/resource.h index 5077f6ccf..754eb69bd 100644 --- a/tools/assimp_view/resource.h +++ b/tools/assimp_view/resource.h @@ -223,7 +223,7 @@ #define IDC_STATIC -1 // Next default values for new objects -// +// #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NO_MFC 1 From 3f6a371b648ab3dc4971859488473064cb0276a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Martin?= Date: Sun, 12 Sep 2021 11:37:33 +0200 Subject: [PATCH 07/28] migrated x3d importer to pugixml reader --- code/AssetLib/X3D/X3DGeoHelper.cpp | 2 +- code/AssetLib/X3D/X3DGeoHelper.h | 2 +- code/AssetLib/X3D/X3DImporter.cpp | 2702 +---------------- code/AssetLib/X3D/X3DImporter.hpp | 531 +--- code/AssetLib/X3D/X3DImporter_Geometry2D.cpp | 467 +++ code/AssetLib/X3D/X3DImporter_Geometry3D.cpp | 981 ++++++ code/AssetLib/X3D/X3DImporter_Group.cpp | 273 ++ code/AssetLib/X3D/X3DImporter_Light.cpp | 270 ++ code/AssetLib/X3D/X3DImporter_Macro.hpp | 110 + code/AssetLib/X3D/X3DImporter_Metadata.cpp | 255 ++ code/AssetLib/X3D/X3DImporter_Networking.cpp | 125 + code/AssetLib/X3D/X3DImporter_Node.hpp | 459 +++ code/AssetLib/X3D/X3DImporter_Postprocess.cpp | 731 +++++ code/AssetLib/X3D/X3DImporter_Rendering.cpp | 987 ++++++ code/AssetLib/X3D/X3DImporter_Shape.cpp | 221 ++ code/AssetLib/X3D/X3DImporter_Texturing.cpp | 179 ++ code/AssetLib/X3D/X3DXmlHelper.cpp | 294 ++ code/AssetLib/X3D/X3DXmlHelper.h | 30 + code/CMakeLists.txt | 14 + code/Common/ImporterRegistry.cpp | 4 +- 20 files changed, 5662 insertions(+), 2975 deletions(-) create mode 100644 code/AssetLib/X3D/X3DImporter_Geometry2D.cpp create mode 100644 code/AssetLib/X3D/X3DImporter_Geometry3D.cpp create mode 100644 code/AssetLib/X3D/X3DImporter_Group.cpp create mode 100644 code/AssetLib/X3D/X3DImporter_Light.cpp create mode 100644 code/AssetLib/X3D/X3DImporter_Macro.hpp create mode 100644 code/AssetLib/X3D/X3DImporter_Metadata.cpp create mode 100644 code/AssetLib/X3D/X3DImporter_Networking.cpp create mode 100644 code/AssetLib/X3D/X3DImporter_Node.hpp create mode 100644 code/AssetLib/X3D/X3DImporter_Postprocess.cpp create mode 100644 code/AssetLib/X3D/X3DImporter_Rendering.cpp create mode 100644 code/AssetLib/X3D/X3DImporter_Shape.cpp create mode 100644 code/AssetLib/X3D/X3DImporter_Texturing.cpp create mode 100644 code/AssetLib/X3D/X3DXmlHelper.cpp create mode 100644 code/AssetLib/X3D/X3DXmlHelper.h diff --git a/code/AssetLib/X3D/X3DGeoHelper.cpp b/code/AssetLib/X3D/X3DGeoHelper.cpp index be41cc012..a9ac57e06 100644 --- a/code/AssetLib/X3D/X3DGeoHelper.cpp +++ b/code/AssetLib/X3D/X3DGeoHelper.cpp @@ -116,7 +116,7 @@ void X3DGeoHelper::polylineIdx_to_lineIdx(const std::list &pPolylineCoo vert_set[6].Set(x1, y2, z1); \ vert_set[7].Set(x1, y1, z1) -void X3DGeoHelper::rect_parallele_piped(const aiVector3D &pSize, std::list &pVertices) { +void X3DGeoHelper::rect_parallel_epiped(const aiVector3D &pSize, std::list &pVertices) { MESH_RectParallelepiped_CREATE_VERT; MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 3, 2, 1, 0); // front MACRO_FACE_ADD_QUAD_FA(true, pVertices, vert_set, 6, 7, 4, 5); // back diff --git a/code/AssetLib/X3D/X3DGeoHelper.h b/code/AssetLib/X3D/X3DGeoHelper.h index e730d3a9c..78e57f9da 100644 --- a/code/AssetLib/X3D/X3DGeoHelper.h +++ b/code/AssetLib/X3D/X3DGeoHelper.h @@ -19,7 +19,7 @@ public: static void make_arc2D(float pStartAngle, float pEndAngle, float pRadius, size_t numSegments, std::list &pVertices); static void extend_point_to_line(const std::list &pPoint, std::list &pLine); static void polylineIdx_to_lineIdx(const std::list &pPolylineCoordIdx, std::list &pLineCoordIdx); - static void rect_parallele_piped(const aiVector3D &pSize, std::list &pVertices); + static void rect_parallel_epiped(const aiVector3D &pSize, std::list &pVertices); static void coordIdx_str2faces_arr(const std::vector &pCoordIdx, std::vector &pFaces, unsigned int &pPrimitiveTypes); static void add_color(aiMesh &pMesh, const std::list &pColors, const bool pColorPerVertex); static void add_color(aiMesh &pMesh, const std::list &pColors, const bool pColorPerVertex); diff --git a/code/AssetLib/X3D/X3DImporter.cpp b/code/AssetLib/X3D/X3DImporter.cpp index eeb1a53d7..bfe83a49f 100644 --- a/code/AssetLib/X3D/X3DImporter.cpp +++ b/code/AssetLib/X3D/X3DImporter.cpp @@ -46,11 +46,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_X3D_IMPORTER #include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" -#include -#include #include -#include // Header files, stdlib. #include @@ -72,59 +70,15 @@ const aiImporterDesc X3DImporter::Description = { "x3d x3db" }; -struct WordIterator { - using iterator_category = std::input_iterator_tag; - using value_type = const char *; - using difference_type = ptrdiff_t; - using pointer = value_type *; - using reference = value_type &; +bool X3DImporter::isNodeEmpty(XmlNode &node) { + return node.first_child().empty(); +} - static const char *whitespace; - const char *mStart, *mEnd; +void X3DImporter::checkNodeMustBeEmpty(XmlNode &node) { + if (isNodeEmpty(node)) throw DeadlyImportError(std::string("Node <") + node.name() + "> must be empty."); +} - WordIterator(const char *start, const char *end) : - mStart(start), - mEnd(end) { - mStart = start + ::strspn(start, whitespace); - if (mStart >= mEnd) { - mStart = 0; - } - } - WordIterator() : - mStart(0), - mEnd(0) {} - WordIterator(const WordIterator &other) : - mStart(other.mStart), - mEnd(other.mEnd) {} - WordIterator &operator=(const WordIterator &other) { - mStart = other.mStart; - mEnd = other.mEnd; - return *this; - } - - bool operator==(const WordIterator &other) const { return mStart == other.mStart; } - - bool operator!=(const WordIterator &other) const { return mStart != other.mStart; } - - WordIterator &operator++() { - mStart += strcspn(mStart, whitespace); - mStart += strspn(mStart, whitespace); - if (mStart >= mEnd) { - mStart = 0; - } - return *this; - } - WordIterator operator++(int) { - WordIterator result(*this); - ++(*this); - return result; - } - const char *operator*() const { return mStart; } -}; - -const char *WordIterator::whitespace = ", \t\r\n"; - -void skipUnsupportedNode(const std::string &pParentNodeName, XmlNode &node) { +void X3DImporter::skipUnsupportedNode(const std::string &pParentNodeName, XmlNode &node) { static const size_t Uns_Skip_Len = 192; static const char *Uns_Skip[Uns_Skip_Len] = { // CAD geometry component @@ -203,16 +157,20 @@ void skipUnsupportedNode(const std::string &pParentNodeName, XmlNode &node) { }; const std::string nn = node.name(); + + if (nn.empty()) { + const std::string nv = node.value(); + if (!nv.empty()) { + LogInfo("Ignoring comment \"" + nv + "\" in " + pParentNodeName + "."); + return; + } + } + bool found = false; - bool close_found = false; for (size_t i = 0; i < Uns_Skip_Len; i++) { if (nn == Uns_Skip[i]) { found = true; - if (node.empty()) { - close_found = true; - break; - } } } @@ -223,7 +181,8 @@ void skipUnsupportedNode(const std::string &pParentNodeName, XmlNode &node) { X3DImporter::X3DImporter() : mNodeElementCur(nullptr), - mScene(nullptr) { + mScene(nullptr), + mpIOHandler(nullptr) { // empty } @@ -265,9 +224,11 @@ void X3DImporter::ParseFile(const std::string &file, IOSystem *pIOHandler) { for (auto ¤tNode : node->children()) { const std::string ¤tName = currentNode.name(); if (currentName == "head") { - readMetadata(currentNode); + readHead(currentNode); } else if (currentName == "Scene") { readScene(currentNode); + } else { + skipUnsupportedNode("X3D", currentNode); } } } @@ -283,23 +244,25 @@ bool X3DImporter::CanRead(const std::string &pFile, IOSystem * /*pIOHandler*/, b return false; } -void X3DImporter::InternReadFile( const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler ) { +void X3DImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + mpIOHandler = pIOHandler; + + Clear(); std::shared_ptr stream(pIOHandler->Open(pFile, "rb")); if (!stream) { throw DeadlyImportError("Could not open file for reading"); } std::string::size_type slashPos = pFile.find_last_of("\\/"); - pIOHandler->PushDirectory(slashPos == std::string::npos ? std::string() : pFile.substr(0, slashPos + 1)); - ParseFile(pFile, pIOHandler); - pIOHandler->PopDirectory(); - - // mScene = pScene; pScene->mRootNode = new aiNode(pFile); pScene->mRootNode->mParent = nullptr; pScene->mFlags |= AI_SCENE_FLAGS_ALLOW_SHARED; + pIOHandler->PushDirectory(slashPos == std::string::npos ? std::string() : pFile.substr(0, slashPos + 1)); + ParseFile(pFile, pIOHandler); + pIOHandler->PopDirectory(); + //search for root node element mNodeElementCur = NodeElement_List.front(); @@ -342,7 +305,6 @@ void X3DImporter::InternReadFile( const std::string &pFile, aiScene *pScene, IOS pScene->mLights[i] = *it++; } } - } const aiImporterDesc *X3DImporter::GetInfo() const { @@ -354,2540 +316,162 @@ struct meta_entry { std::string value; }; -void X3DImporter::readMetadata(XmlNode &node) { +void X3DImporter::readHead(XmlNode &node) { std::vector metaArray; for (auto currentNode : node.children()) { const std::string ¤tName = currentNode.name(); if (currentName == "meta") { + checkNodeMustBeEmpty(node); meta_entry entry; if (XmlParser::getStdStrAttribute(currentNode, "name", entry.name)) { XmlParser::getStdStrAttribute(currentNode, "content", entry.value); metaArray.emplace_back(entry); } } + // TODO: check if other node types in head should be supported } mScene->mMetaData = aiMetadata::Alloc(static_cast(metaArray.size())); unsigned int i = 0; for (auto currentMeta : metaArray) { - mScene->mMetaData->Set(i, currentMeta.name, currentMeta.value); + mScene->mMetaData->Set(i, currentMeta.name, aiString(currentMeta.value)); ++i; } } -void X3DImporter::readScene(XmlNode &node) { +void X3DImporter::readChildNodes(XmlNode &node, const std::string &pParentNodeName) { for (auto currentNode : node.children()) { const std::string ¤tName = currentNode.name(); - if (currentName == "Viewpoint") { - readViewpoint(currentNode); + if (currentName == "Shape") + readShape(currentNode); + else if (currentName == "Group") { + startReadGroup(currentNode); + readChildNodes(currentNode, "Group"); + endReadGroup(); + } else if (currentName == "StaticGroup") { + startReadStaticGroup(currentNode); + readChildNodes(currentNode, "StaticGroup"); + endReadStaticGroup(); + } else if (currentName == "Transform") { + startReadTransform(currentNode); + readChildNodes(currentNode, "Transform"); + endReadTransform(); + } else if (currentName == "Switch") { + startReadSwitch(currentNode); + readChildNodes(currentNode, "Switch"); + endReadSwitch(); + } else if (currentName == "DirectionalLight") { + readDirectionalLight(currentNode); + } else if (currentName == "PointLight") { + readPointLight(currentNode); + } else if (currentName == "SpotLight") { + readSpotLight(currentNode); + } else if (currentName == "Inline") { + readInline(currentNode); + } else if (!checkForMetadataNode(currentNode)) { + skipUnsupportedNode(pParentNodeName, currentNode); } } } -void X3DImporter::readViewpoint(XmlNode &node) { - for (auto currentNode : node.children()) { - //const std::string ¤tName = currentNode.name(); - } +void X3DImporter::readScene(XmlNode &node) { + ParseHelper_Group_Begin(true); + readChildNodes(node, "Scene"); + ParseHelper_Node_Exit(); } -void readMetadataBoolean(XmlNode &node, X3DNodeElementBase *parent) { - std::string val; - X3DNodeElementMetaBoolean *boolean = nullptr; - if (XmlParser::getStdStrAttribute(node, "value", val)) { - std::vector values; - tokenize(val, values, " "); - boolean = new X3DNodeElementMetaBoolean(parent); - for (size_t i = 0; i < values.size(); ++i) { - bool current_boolean = false; - if (values[i] == "true") { - current_boolean = true; - } - boolean->Value.emplace_back(current_boolean); +/*********************************************************************************************************************************************/ +/************************************************************ Functions: find set ************************************************************/ +/*********************************************************************************************************************************************/ + +bool X3DImporter::FindNodeElement_FromRoot(const std::string &pID, const X3DElemType pType, X3DNodeElementBase **pElement) { + for (std::list::iterator it = NodeElement_List.begin(); it != NodeElement_List.end(); ++it) { + if (((*it)->Type == pType) && ((*it)->ID == pID)) { + if (pElement != nullptr) *pElement = *it; + + return true; } - } -} + } // for(std::list::iterator it = NodeElement_List.begin(); it != NodeElement_List.end(); it++) -void readMetadataDouble(XmlNode &node, X3DNodeElementBase *parent) { - std::string val; - X3DNodeElementMetaDouble *doubleNode = nullptr; - if (XmlParser::getStdStrAttribute(node, "value", val)) { - std::vector values; - tokenize(val, values, " "); - doubleNode = new X3DNodeElementMetaDouble(parent); - for (size_t i = 0; i < values.size(); ++i) { - double current_double = static_cast(fast_atof(values[i].c_str())); - doubleNode->Value.emplace_back(current_double); - } - } -} - -void readMetadataFloat(XmlNode &node, X3DNodeElementBase *parent) { - std::string val; - X3DNodeElementMetaFloat *floatNode = nullptr; - if (XmlParser::getStdStrAttribute(node, "value", val)) { - std::vector values; - tokenize(val, values, " "); - floatNode = new X3DNodeElementMetaFloat(parent); - for (size_t i = 0; i < values.size(); ++i) { - float current_float = static_cast(fast_atof(values[i].c_str())); - floatNode->Value.emplace_back(current_float); - } - } -} - -void readMetadataInteger(XmlNode &node, X3DNodeElementBase *parent) { - std::string val; - X3DNodeElementMetaInt *intNode = nullptr; - if (XmlParser::getStdStrAttribute(node, "value", val)) { - std::vector values; - tokenize(val, values, " "); - intNode = new X3DNodeElementMetaInt(parent); - for (size_t i = 0; i < values.size(); ++i) { - int current_int = static_cast(std::atoi(values[i].c_str())); - intNode->Value.emplace_back(current_int); - } - } -} - -void readMetadataSet(XmlNode &node, X3DNodeElementBase *parent) { - std::string val; - X3DNodeElementMetaSet *setNode = new X3DNodeElementMetaSet(parent); - if (XmlParser::getStdStrAttribute(node, "name", val)) { - setNode->Name = val; - } - - if (XmlParser::getStdStrAttribute(node, "reference", val)) { - setNode->Reference = val; - } -} - -void readMetadataString(XmlNode &node, X3DNodeElementBase *parent) { - std::string val; - X3DNodeElementMetaString *strNode = nullptr; - if (XmlParser::getStdStrAttribute(node, "value", val)) { - std::vector values; - tokenize(val, values, " "); - strNode = new X3DNodeElementMetaString(parent); - for (size_t i = 0; i < values.size(); ++i) { - strNode->Value.emplace_back(values[i]); - } - } -} - -void X3DImporter::ParseDirectionalLight(XmlNode &node) { - std::string def, use; - float ambientIntensity = 0; - aiColor3D color(1, 1, 1); - aiVector3D direction(0, 0, -1); - bool global = false; - float intensity = 1; - bool on = true; - X3DNodeElementBase *ne = nullptr; - - //MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - XmlParser::getFloatAttribute(node, "ambientIntensity", ambientIntensity); - //MACRO_ATTRREAD_CHECK_RET("ambientIntensity", ambientIntensity, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_REF("color", color, XML_ReadNode_GetAttrVal_AsCol3f); - MACRO_ATTRREAD_CHECK_REF("direction", direction, XML_ReadNode_GetAttrVal_AsVec3f); - MACRO_ATTRREAD_CHECK_RET("global", global, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("intensity", intensity, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_RET("on", on, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(def, use, ENET_DirectionalLight, ne); - } else { - if (on) { - // create and if needed - define new geometry object. - ne = new X3DNodeNodeElementLight(CX3DImporter_NodeElement::ENET_DirectionalLight, NodeElement_Cur); - if (!def.empty()) - ne->ID = def; - else - ne->ID = "DirectionalLight_" + to_string((size_t)ne); // make random name - - ((X3DNodeNodeElementLight *)ne)->AmbientIntensity = ambientIntensity; - ((X3DNodeNodeElementLight *)ne)->Color = color; - ((X3DNodeNodeElementLight *)ne)->Direction = direction; - ((X3DNodeNodeElementLight *)ne)->Global = global; - ((X3DNodeNodeElementLight *)ne)->Intensity = intensity; - // Assimp want a node with name similar to a light. "Why? I don't no." ) - ParseHelper_Group_Begin(false); - - mNodeElementCur->ID = ne->ID; // assign name to node and return to light element. - ParseHelper_Node_Exit(); - // check for child nodes - if (!mReader->isEmptyElement()) - ParseNode_Metadata(ne, "DirectionalLight"); - else - mNodeElementCur->Child.push_back(ne); // add made object as child to current element - - NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph - } // if(on) - } // if(!use.empty()) else -} - -// -void X3DImporter::ParseNode_Lighting_PointLight() { - std::string def, use; - float ambientIntensity = 0; - aiVector3D attenuation(1, 0, 0); - aiColor3D color(1, 1, 1); - bool global = true; - float intensity = 1; - aiVector3D location(0, 0, 0); - bool on = true; - float radius = 100; - X3DNodeElementBase *ne = nullptr; - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_RET("ambientIntensity", ambientIntensity, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_REF("attenuation", attenuation, XML_ReadNode_GetAttrVal_AsVec3f); - MACRO_ATTRREAD_CHECK_REF("color", color, XML_ReadNode_GetAttrVal_AsCol3f); - MACRO_ATTRREAD_CHECK_RET("global", global, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("intensity", intensity, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_REF("location", location, XML_ReadNode_GetAttrVal_AsVec3f); - MACRO_ATTRREAD_CHECK_RET("on", on, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("radius", radius, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(def, use, ENET_PointLight, ne); - } else { - if (on) { - // create and if needed - define new geometry object. - ne = new X3DNodeNodeElementLight(X3DElemType::ENET_PointLight, mNodeElementCur); - if (!def.empty()) ne->ID = def; - - ((X3DNodeNodeElementLight *)ne)->AmbientIntensity = ambientIntensity; - ((X3DNodeNodeElementLight *)ne)->Attenuation = attenuation; - ((X3DNodeNodeElementLight *)ne)->Color = color; - ((X3DNodeNodeElementLight *)ne)->Global = global; - ((X3DNodeNodeElementLight *)ne)->Intensity = intensity; - ((X3DNodeNodeElementLight *)ne)->Location = location; - ((X3DNodeNodeElementLight *)ne)->Radius = radius; - // Assimp want a node with name similar to a light. "Why? I don't no." ) - ParseHelper_Group_Begin(false); - // make random name - if (ne->ID.empty()) ne->ID = "PointLight_" + to_string((size_t)ne); - - mNodeElementCur->ID = ne->ID; // assign name to node and return to light element. - ParseHelper_Node_Exit(); - // check for child nodes - if (!mReader->isEmptyElement()) - ParseNode_Metadata(ne, "PointLight"); - else - mNodeElementCur->Child.push_back(ne); // add made object as child to current element - - NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph - } // if(on) - } // if(!use.empty()) else -} - -// -void X3DImporter::ParseNode_Lighting_SpotLight() { - std::string def, use; - float ambientIntensity = 0; - aiVector3D attenuation(1, 0, 0); - float beamWidth = 0.7854f; - aiColor3D color(1, 1, 1); - float cutOffAngle = 1.570796f; - aiVector3D direction(0, 0, -1); - bool global = true; - float intensity = 1; - aiVector3D location(0, 0, 0); - bool on = true; - float radius = 100; - X3DNodeElementBase *ne = nullptr; - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_RET("ambientIntensity", ambientIntensity, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_REF("attenuation", attenuation, XML_ReadNode_GetAttrVal_AsVec3f); - MACRO_ATTRREAD_CHECK_RET("beamWidth", beamWidth, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_REF("color", color, XML_ReadNode_GetAttrVal_AsCol3f); - MACRO_ATTRREAD_CHECK_RET("cutOffAngle", cutOffAngle, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_REF("direction", direction, XML_ReadNode_GetAttrVal_AsVec3f); - MACRO_ATTRREAD_CHECK_RET("global", global, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("intensity", intensity, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_REF("location", location, XML_ReadNode_GetAttrVal_AsVec3f); - MACRO_ATTRREAD_CHECK_RET("on", on, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("radius", radius, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(def, use, ENET_SpotLight, ne); - } else { - if (on) { - // create and if needed - define new geometry object. - ne = new X3DNodeNodeElementLight(X3DElemType::ENET_SpotLight, mNodeElementCur); - if (!def.empty()) - ne->ID = def; - - if (beamWidth > cutOffAngle) - beamWidth = cutOffAngle; - - ((X3DNodeNodeElementLight *)ne)->AmbientIntensity = ambientIntensity; - ((X3DNodeNodeElementLight *)ne)->Attenuation = attenuation; - ((X3DNodeNodeElementLight *)ne)->BeamWidth = beamWidth; - ((X3DNodeNodeElementLight *)ne)->Color = color; - ((X3DNodeNodeElementLight *)ne)->CutOffAngle = cutOffAngle; - ((X3DNodeNodeElementLight *)ne)->Direction = direction; - ((X3DNodeNodeElementLight *)ne)->Global = global; - ((X3DNodeNodeElementLight *)ne)->Intensity = intensity; - ((X3DNodeNodeElementLight *)ne)->Location = location; - ((X3DNodeNodeElementLight *)ne)->Radius = radius; - - // Assimp want a node with name similar to a light. "Why? I don't no." ) - ParseHelper_Group_Begin(false); - // make random name - if (ne->ID.empty()) ne->ID = "SpotLight_" + to_string((size_t)ne); - - mNodeElementCur->ID = ne->ID; // assign name to node and return to light element. - ParseHelper_Node_Exit(); - // check for child nodes - if (!mReader->isEmptyElement()) - ParseNode_Metadata(ne, "SpotLight"); - else - mNodeElementCur->Child.push_back(ne); // add made object as child to current element - - NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph - } // if(on) - } // if(!use.empty()) else -} - -void X3DImporter::ParseNode_Grouping_Group() { - std::string def, use; - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - X3DNodeElementBase *ne = nullptr; - - MACRO_USE_CHECKANDAPPLY(def, use, ENET_Group, ne); - } else { - ParseHelper_Group_Begin(); // create new grouping element and go deeper if node has children. - // at this place new group mode created and made current, so we can name it. - if (!def.empty()) mNodeElementCur->ID = def; - // in grouping set of nodes check X3DMetadataObject is not needed, because it is done in parser function. - - // for empty element exit from node in that place - if (mReader->isEmptyElement()) ParseHelper_Node_Exit(); - } // if(!use.empty()) else -} - -void X3DImporter::ParseNode_Grouping_GroupEnd() { - ParseHelper_Node_Exit(); // go up in scene graph -} - -// -// -// ChildContentModel is the child-node content model corresponding to X3DChildNode, combining all profiles. ChildContentModel can contain most nodes, -// other Grouping nodes, Prototype declarations and ProtoInstances in any order and any combination. When the assigned profile is less than Full, the -// precise palette of legal nodes that are available depends on assigned profile and components. -// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. -// -// The StaticGroup node contains children nodes which cannot be modified. StaticGroup children are guaranteed to not change, send events, receive events or -// contain any USE references outside the StaticGroup. -void X3DImporter::ParseNode_Grouping_StaticGroup() { - std::string def, use; - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - X3DNodeElementBase *ne = nullptr; - - MACRO_USE_CHECKANDAPPLY(def, use, ENET_Group, ne); - } else { - ParseHelper_Group_Begin(true); // create new grouping element and go deeper if node has children. - // at this place new group mode created and made current, so we can name it. - if (!def.empty()) mNodeElementCur->ID = def; - // in grouping set of nodes check X3DMetadataObject is not needed, because it is done in parser function. - - // for empty element exit from node in that place - if (mReader->isEmptyElement()) ParseHelper_Node_Exit(); - } // if(!use.empty()) else -} - -void X3DImporter::ParseNode_Grouping_StaticGroupEnd() { - ParseHelper_Node_Exit(); // go up in scene graph -} - -// -// -// ChildContentModel is the child-node content model corresponding to X3DChildNode, combining all profiles. ChildContentModel can contain most nodes, -// other Grouping nodes, Prototype declarations and ProtoInstances in any order and any combination. When the assigned profile is less than Full, the -// precise palette of legal nodes that are available depends on assigned profile and components. -// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. -// -// The Switch grouping node traverses zero or one of the nodes specified in the children field. The whichChoice field specifies the index of the child -// to traverse, with the first child having index 0. If whichChoice is less than zero or greater than the number of nodes in the children field, nothing -// is chosen. -void X3DImporter::ParseNode_Grouping_Switch() { - std::string def, use; - int32_t whichChoice = -1; - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_RET("whichChoice", whichChoice, XML_ReadNode_GetAttrVal_AsI32); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - CX3DImporter_NodeElement *ne; - - MACRO_USE_CHECKANDAPPLY(def, use, ENET_Group, ne); - } else { - ParseHelper_Group_Begin(); // create new grouping element and go deeper if node has children. - // at this place new group mode created and made current, so we can name it. - if (!def.empty()) NodeElement_Cur->ID = def; - - // also set values specific to this type of group - ((CX3DNodeElementGroup *)NodeElement_Cur)->UseChoice = true; - ((CX3DNodeElementGroup *)NodeElement_Cur)->Choice = whichChoice; - // in grouping set of nodes check X3DMetadataObject is not needed, because it is done in parser function. - - // for empty element exit from node in that place - if (mReader->isEmptyElement()) ParseHelper_Node_Exit(); - } // if(!use.empty()) else -} - -void X3DImporter::ParseNode_Grouping_SwitchEnd() { - // just exit from node. Defined choice will be accepted at postprocessing stage. - ParseHelper_Node_Exit(); // go up in scene graph -} - -// -// -// ChildContentModel is the child-node content model corresponding to X3DChildNode, combining all profiles. ChildContentModel can contain most nodes, -// other Grouping nodes, Prototype declarations and ProtoInstances in any order and any combination. When the assigned profile is less than Full, the -// precise palette of legal nodes that are available depends on assigned profile and components. -// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. -// -// The Transform node is a grouping node that defines a coordinate system for its children that is relative to the coordinate systems of its ancestors. -// Given a 3-dimensional point P and Transform node, P is transformed into point P' in its parent's coordinate system by a series of intermediate -// transformations. In matrix transformation notation, where C (center), SR (scaleOrientation), T (translation), R (rotation), and S (scale) are the -// equivalent transformation matrices, -// P' = T * C * R * SR * S * -SR * -C * P -void X3DImporter::ParseNode_Grouping_Transform() { - aiVector3D center(0, 0, 0); - float rotation[4] = { 0, 0, 1, 0 }; - aiVector3D scale(1, 1, 1); // A value of zero indicates that any child geometry shall not be displayed - float scale_orientation[4] = { 0, 0, 1, 0 }; - aiVector3D translation(0, 0, 0); - aiMatrix4x4 matr, tmatr; - std::string use, def; - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_REF("center", center, XML_ReadNode_GetAttrVal_AsVec3f); - MACRO_ATTRREAD_CHECK_REF("scale", scale, XML_ReadNode_GetAttrVal_AsVec3f); - MACRO_ATTRREAD_CHECK_REF("translation", translation, XML_ReadNode_GetAttrVal_AsVec3f); - if (an == "rotation") { - std::vector tvec; - - XML_ReadNode_GetAttrVal_AsArrF(idx, tvec); - if (tvec.size() != 4) throw DeadlyImportError(": rotation vector must have 4 elements."); - - memcpy(rotation, tvec.data(), sizeof(rotation)); - - continue; - } - - if (an == "scaleOrientation") { - std::vector tvec; - XML_ReadNode_GetAttrVal_AsArrF(idx, tvec); - if (tvec.size() != 4) { - throw DeadlyImportError(": scaleOrientation vector must have 4 elements."); - } - - ::memcpy(scale_orientation, tvec.data(), sizeof(scale_orientation)); - - continue; - } - - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - CX3DImporter_NodeElement *ne(nullptr); - - MACRO_USE_CHECKANDAPPLY(def, use, ENET_Group, ne); - } else { - ParseHelper_Group_Begin(); // create new grouping element and go deeper if node has children. - // at this place new group mode created and made current, so we can name it. - if (!def.empty()) { - NodeElement_Cur->ID = def; - } - - // - // also set values specific to this type of group - // - // calculate transformation matrix - aiMatrix4x4::Translation(translation, matr); // T - aiMatrix4x4::Translation(center, tmatr); // C - matr *= tmatr; - aiMatrix4x4::Rotation(rotation[3], aiVector3D(rotation[0], rotation[1], rotation[2]), tmatr); // R - matr *= tmatr; - aiMatrix4x4::Rotation(scale_orientation[3], aiVector3D(scale_orientation[0], scale_orientation[1], scale_orientation[2]), tmatr); // SR - matr *= tmatr; - aiMatrix4x4::Scaling(scale, tmatr); // S - matr *= tmatr; - aiMatrix4x4::Rotation(-scale_orientation[3], aiVector3D(scale_orientation[0], scale_orientation[1], scale_orientation[2]), tmatr); // -SR - matr *= tmatr; - aiMatrix4x4::Translation(-center, tmatr); // -C - matr *= tmatr; - // and assign it - ((CX3DNodeElementGroup *)mNodeElementCur)->Transformation = matr; - // in grouping set of nodes check X3DMetadataObject is not needed, because it is done in parser function. - - // for empty element exit from node in that place - if (mReader->isEmptyElement()) { - ParseHelper_Node_Exit(); - } - } // if(!use.empty()) else -} - -void X3DImporter::ParseNode_Grouping_TransformEnd() { - ParseHelper_Node_Exit(); // go up in scene graph -} - -void X3DImporter::ParseNode_Geometry2D_Arc2D() { - std::string def, use; - float endAngle = AI_MATH_HALF_PI_F; - float radius = 1; - float startAngle = 0; - X3DNodeElementBase *ne(nullptr); - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_RET("endAngle", endAngle, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_RET("radius", radius, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_RET("startAngle", startAngle, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(def, use, ENET_Arc2D, ne); - } else { - // create and if needed - define new geometry object. - ne = new CX3DImporter_NodeElement_Geometry2D(CX3DImporter_NodeElement::ENET_Arc2D, NodeElement_Cur); - if (!def.empty()) ne->ID = def; - - // create point list of geometry object and convert it to line set. - std::list tlist; - - GeometryHelper_Make_Arc2D(startAngle, endAngle, radius, 10, tlist); ///TODO: IME - AI_CONFIG for NumSeg - GeometryHelper_Extend_PointToLine(tlist, ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices); - ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = 2; - // check for X3DMetadataObject childs. - if (!mReader->isEmptyElement()) - ParseNode_Metadata(ne, "Arc2D"); - else - NodeElement_Cur->Child.push_back(ne); // add made object as child to current element - - NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph - } // if(!use.empty()) else -} - -// -// The ArcClose node specifies a portion of a circle whose center is at (0,0) and whose angles are measured starting at the positive x-axis and sweeping -// towards the positive y-axis. The end points of the arc specified are connected as defined by the closureType field. The radius field specifies the radius -// of the circle of which the arc is a portion. The arc extends from the startAngle counterclockwise to the endAngle. The value of radius shall be greater -// than zero. The values of startAngle and endAngle shall be in the range [-2pi, 2pi] radians (or the equivalent if a different default angle base unit has -// been specified). If startAngle and endAngle have the same value, a circle is specified and closureType is ignored. If the absolute difference between -// startAngle and endAngle is greater than or equal to 2pi, a complete circle is produced with no chord or radial line(s) drawn from the center. -// A closureType of "PIE" connects the end point to the start point by defining two straight line segments first from the end point to the center and then -// the center to the start point. A closureType of "CHORD" connects the end point to the start point by defining a straight line segment from the end point -// to the start point. Textures are applied individually to each face of the ArcClose2D. On the front (+Z) and back (-Z) faces of the ArcClose2D, when -// viewed from the +Z-axis, the texture is mapped onto each face with the same orientation as if the image were displayed normally in 2D. -void X3DImporter::ParseNode_Geometry2D_ArcClose2D() { - std::string def, use; - std::string closureType("PIE"); - float endAngle = AI_MATH_HALF_PI_F; - float radius = 1; - bool solid = false; - float startAngle = 0; - X3DNodeElementBase *ne(nullptr); - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_RET("closureType", closureType, mReader->getAttributeValue); - MACRO_ATTRREAD_CHECK_RET("endAngle", endAngle, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_RET("radius", radius, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("startAngle", startAngle, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(def, use, ENET_ArcClose2D, ne); - } else { - // create and if needed - define new geometry object. - ne = new CX3DImporter_NodeElement_Geometry2D(CX3DImporter_NodeElement::ENET_ArcClose2D, NodeElement_Cur); - if (!def.empty()) ne->ID = def; - - ((CX3DImporter_NodeElement_Geometry2D *)ne)->Solid = solid; - // create point list of geometry object. - GeometryHelper_Make_Arc2D(startAngle, endAngle, radius, 10, ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices); ///TODO: IME - AI_CONFIG for NumSeg - // add chord or two radiuses only if not a circle was defined - if (!((std::fabs(endAngle - startAngle) >= AI_MATH_TWO_PI_F) || (endAngle == startAngle))) { - std::list &vlist = ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices; // just short alias. - - if ((closureType == "PIE") || (closureType == "\"PIE\"")) - vlist.push_back(aiVector3D(0, 0, 0)); // center point - first radial line - else if ((closureType != "CHORD") && (closureType != "\"CHORD\"")) - Throw_IncorrectAttrValue("closureType"); - - vlist.push_back(*vlist.begin()); // arc first point - chord from first to last point of arc(if CHORD) or second radial line(if PIE). - } - - ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices.size(); - // check for X3DMetadataObject childs. - if (!mReader->isEmptyElement()) - ParseNode_Metadata(ne, "ArcClose2D"); - else - NodeElement_Cur->Child.push_back(ne); // add made object as child to current element - - NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph - } // if(!use.empty()) else -} - -// -void X3DImporter::ParseNode_Geometry2D_Circle2D() { - std::string def, use; - float radius = 1; - X3DNodeElementBase *ne(nullptr); - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_RET("radius", radius, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(def, use, ENET_Circle2D, ne); - } else { - // create and if needed - define new geometry object. - ne = new CX3DImporter_NodeElement_Geometry2D(CX3DImporter_NodeElement::ENET_Circle2D, NodeElement_Cur); - if (!def.empty()) ne->ID = def; - - // create point list of geometry object and convert it to line set. - std::list tlist; - - GeometryHelper_Make_Arc2D(0, 0, radius, 10, tlist); ///TODO: IME - AI_CONFIG for NumSeg - GeometryHelper_Extend_PointToLine(tlist, ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices); - ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = 2; - // check for X3DMetadataObject childs. - if (!mReader->isEmptyElement()) - ParseNode_Metadata(ne, "Circle2D"); - else - NodeElement_Cur->Child.push_back(ne); // add made object as child to current element - - NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph - } // if(!use.empty()) else -} - -// -// The Disk2D node specifies a circular disk which is centred at (0, 0) in the local coordinate system. The outerRadius field specifies the radius of the -// outer dimension of the Disk2D. The innerRadius field specifies the inner dimension of the Disk2D. The value of outerRadius shall be greater than zero. -// The value of innerRadius shall be greater than or equal to zero and less than or equal to outerRadius. If innerRadius is zero, the Disk2D is completely -// filled. Otherwise, the area within the innerRadius forms a hole in the Disk2D. If innerRadius is equal to outerRadius, a solid circular line shall -// be drawn using the current line properties. Textures are applied individually to each face of the Disk2D. On the front (+Z) and back (-Z) faces of -// the Disk2D, when viewed from the +Z-axis, the texture is mapped onto each face with the same orientation as if the image were displayed normally in 2D. -void X3DImporter::ParseNode_Geometry2D_Disk2D() { - std::string def, use; - float innerRadius = 0; - float outerRadius = 1; - bool solid = false; - X3DNodeElementBase *ne(nullptr); - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_RET("innerRadius", innerRadius, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_RET("outerRadius", outerRadius, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(def, use, ENET_Disk2D, ne); - } else { - std::list tlist_o, tlist_i; - - if (innerRadius > outerRadius) Throw_IncorrectAttrValue("innerRadius"); - - // create and if needed - define new geometry object. - ne = new CX3DImporter_NodeElement_Geometry2D(CX3DImporter_NodeElement::ENET_Disk2D, NodeElement_Cur); - if (!def.empty()) ne->ID = def; - - // create point list of geometry object. - ///TODO: IME - AI_CONFIG for NumSeg - GeometryHelper_Make_Arc2D(0, 0, outerRadius, 10, tlist_o); // outer circle - if (innerRadius == 0.0f) { // make filled disk - // in tlist_o we already have points of circle. just copy it and sign as polygon. - ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices = tlist_o; - ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = tlist_o.size(); - } else if (innerRadius == outerRadius) { // make circle - // in tlist_o we already have points of circle. convert it to line set. - GeometryHelper_Extend_PointToLine(tlist_o, ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices); - ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = 2; - } else { // make disk - std::list &vlist = ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices; // just short alias. - - GeometryHelper_Make_Arc2D(0, 0, innerRadius, 10, tlist_i); // inner circle - // - // create quad list from two point lists - // - if (tlist_i.size() < 2) throw DeadlyImportError("Disk2D. Not enough points for creating quad list."); // tlist_i and tlist_o has equal size. - - // add all quads except last - for (std::list::iterator it_i = tlist_i.begin(), it_o = tlist_o.begin(); it_i != tlist_i.end();) { - // do not forget - CCW direction - vlist.push_back(*it_i++); // 1st point - vlist.push_back(*it_o++); // 2nd point - vlist.push_back(*it_o); // 3rd point - vlist.push_back(*it_i); // 4th point - } - - // add last quad - vlist.push_back(*tlist_i.end()); // 1st point - vlist.push_back(*tlist_o.end()); // 2nd point - vlist.push_back(*tlist_o.begin()); // 3rd point - vlist.push_back(*tlist_o.begin()); // 4th point - - ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = 4; - } - - ((CX3DImporter_NodeElement_Geometry2D *)ne)->Solid = solid; - // check for X3DMetadataObject childs. - if (!mReader->isEmptyElement()) - ParseNode_Metadata(ne, "Disk2D"); - else - mNodeElementCur->Child.push_back(ne); // add made object as child to current element - - NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph - } // if(!use.empty()) else -} - -// -void X3DImporter::ParseNode_Geometry2D_Polyline2D() { - std::string def, use; - std::list lineSegments; - X3DNodeElementBase *ne(nullptr); - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_REF("lineSegments", lineSegments, XML_ReadNode_GetAttrVal_AsListVec2f); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(def, use, ENET_Polyline2D, ne); - } else { - // create and if needed - define new geometry object. - ne = new CX3DImporter_NodeElement_Geometry2D(CX3DImporter_NodeElement::ENET_Polyline2D, NodeElement_Cur); - if (!def.empty()) ne->ID = def; - - // - // convert read point list of geometry object to line set. - // - std::list tlist; - - // convert vec2 to vec3 - for (std::list::iterator it2 = lineSegments.begin(); it2 != lineSegments.end(); ++it2) - tlist.push_back(aiVector3D(it2->x, it2->y, 0)); - - // convert point set to line set - GeometryHelper_Extend_PointToLine(tlist, ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices); - ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = 2; - // check for X3DMetadataObject childs. - if (!mReader->isEmptyElement()) - ParseNode_Metadata(ne, "Polyline2D"); - else - NodeElement_Cur->Child.push_back(ne); // add made object as child to current element - - NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph - } // if(!use.empty()) else -} - -// -void X3DImporter::ParseNode_Geometry2D_Polypoint2D() { - std::string def, use; - std::list point; - CX3DImporter_NodeElement *ne(nullptr); - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_REF("point", point, XML_ReadNode_GetAttrVal_AsListVec2f); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(def, use, ENET_Polypoint2D, ne); - } else { - // create and if needed - define new geometry object. - ne = new CX3DImporter_NodeElement_Geometry2D(CX3DImporter_NodeElement::ENET_Polypoint2D, NodeElement_Cur); - if (!def.empty()) ne->ID = def; - - // convert vec2 to vec3 - for (std::list::iterator it2 = point.begin(); it2 != point.end(); ++it2) { - ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices.push_back(aiVector3D(it2->x, it2->y, 0)); - } - - ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = 1; - // check for X3DMetadataObject childs. - if (!mReader->isEmptyElement()) - ParseNode_Metadata(ne, "Polypoint2D"); - else - NodeElement_Cur->Child.push_back(ne); // add made object as child to current element - - NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph - } // if(!use.empty()) else -} - -// -void X3DImporter::ParseNode_Geometry2D_Rectangle2D() { - std::string def, use; - aiVector2D size(2, 2); - bool solid = false; - CX3DImporter_NodeElement *ne(nullptr); - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_REF("size", size, XML_ReadNode_GetAttrVal_AsVec2f); - MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(def, use, ENET_Rectangle2D, ne); - } else { - // create and if needed - define new geometry object. - ne = new CX3DImporter_NodeElement_Geometry2D(CX3DImporter_NodeElement::ENET_Rectangle2D, NodeElement_Cur); - if (!def.empty()) ne->ID = def; - - float x1 = -size.x / 2.0f; - float x2 = size.x / 2.0f; - float y1 = -size.y / 2.0f; - float y2 = size.y / 2.0f; - std::list &vlist = ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices; // just short alias. - - vlist.push_back(aiVector3D(x2, y1, 0)); // 1st point - vlist.push_back(aiVector3D(x2, y2, 0)); // 2nd point - vlist.push_back(aiVector3D(x1, y2, 0)); // 3rd point - vlist.push_back(aiVector3D(x1, y1, 0)); // 4th point - ((CX3DImporter_NodeElement_Geometry2D *)ne)->Solid = solid; - ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = 4; - // check for X3DMetadataObject childs. - if (!mReader->isEmptyElement()) - ParseNode_Metadata(ne, "Rectangle2D"); - else - NodeElement_Cur->Child.push_back(ne); // add made object as child to current element - - NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph - } // if(!use.empty()) else -} - -// -void X3DImporter::ParseNode_Geometry2D_TriangleSet2D() { - std::string def, use; - bool solid = false; - std::list vertices; - CX3DImporter_NodeElement *ne(nullptr); - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_REF("vertices", vertices, XML_ReadNode_GetAttrVal_AsListVec2f); - MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(def, use, ENET_TriangleSet2D, ne); - } else { - if (vertices.size() % 3) throw DeadlyImportError("TriangleSet2D. Not enough points for defining triangle."); - - // create and if needed - define new geometry object. - ne = new CX3DImporter_NodeElement_Geometry2D(CX3DImporter_NodeElement::ENET_TriangleSet2D, NodeElement_Cur); - if (!def.empty()) ne->ID = def; - - // convert vec2 to vec3 - for (std::list::iterator it2 = vertices.begin(); it2 != vertices.end(); ++it2) { - ((CX3DImporter_NodeElement_Geometry2D *)ne)->Vertices.push_back(aiVector3D(it2->x, it2->y, 0)); - } - - ((CX3DImporter_NodeElement_Geometry2D *)ne)->Solid = solid; - ((CX3DImporter_NodeElement_Geometry2D *)ne)->NumIndices = 3; - // check for X3DMetadataObject childs. - if (!mReader->isEmptyElement()) - ParseNode_Metadata(ne, "TriangleSet2D"); - else - NodeElement_Cur->Child.push_back(ne); // add made object as child to current element - - NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph - } // if(!use.empty()) else -} - -// -// The Box node specifies a rectangular parallelepiped box centred at (0, 0, 0) in the local coordinate system and aligned with the local coordinate axes. -// By default, the box measures 2 units in each dimension, from -1 to +1. The size field specifies the extents of the box along the X-, Y-, and Z-axes -// respectively and each component value shall be greater than zero. -void X3DImporter::ParseNode_Geometry3D_Box() { - std::string def, use; - bool solid = true; - aiVector3D size(2, 2, 2); - CX3DImporter_NodeElement *ne(nullptr); - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_REF("size", size, XML_ReadNode_GetAttrVal_AsVec3f); - MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(def, use, ENET_Box, ne); - } else { - // create and if needed - define new geometry object. - ne = new CX3DImporter_NodeElement_Geometry3D(CX3DImporter_NodeElement::ENET_Box, NodeElement_Cur); - if (!def.empty()) ne->ID = def; - - GeometryHelper_MakeQL_RectParallelepiped(size, ((CX3DImporter_NodeElement_Geometry3D *)ne)->Vertices); // get quad list - ((CX3DImporter_NodeElement_Geometry3D *)ne)->Solid = solid; - ((CX3DImporter_NodeElement_Geometry3D *)ne)->NumIndices = 4; - // check for X3DMetadataObject childs. - if (!mReader->isEmptyElement()) - ParseNode_Metadata(ne, "Box"); - else - NodeElement_Cur->Child.push_back(ne); // add made object as child to current element - - NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph - } // if(!use.empty()) else -} - -// -void X3DImporter::ParseNode_Geometry3D_Cone() { - std::string use, def; - bool bottom = true; - float bottomRadius = 1; - float height = 2; - bool side = true; - bool solid = true; - CX3DImporter_NodeElement *ne(nullptr); - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("side", side, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("bottom", bottom, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("height", height, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_RET("bottomRadius", bottomRadius, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(def, use, ENET_Cone, ne); - } else { - const unsigned int tess = 30; ///TODO: IME tessellation factor through ai_property - - std::vector tvec; // temp array for vertices. - - // create and if needed - define new geometry object. - ne = new CX3DImporter_NodeElement_Geometry3D(CX3DImporter_NodeElement::ENET_Cone, NodeElement_Cur); - if (!def.empty()) ne->ID = def; - - // make cone or parts according to flags. - if (side) { - StandardShapes::MakeCone(height, 0, bottomRadius, tess, tvec, !bottom); - } else if (bottom) { - StandardShapes::MakeCircle(bottomRadius, tess, tvec); - height = -(height / 2); - for (std::vector::iterator it = tvec.begin(); it != tvec.end(); ++it) - it->y = height; // y - because circle made in oXZ. - } - - // copy data from temp array - for (std::vector::iterator it = tvec.begin(); it != tvec.end(); ++it) - ((CX3DImporter_NodeElement_Geometry3D *)ne)->Vertices.push_back(*it); - - ((CX3DImporter_NodeElement_Geometry3D *)ne)->Solid = solid; - ((CX3DImporter_NodeElement_Geometry3D *)ne)->NumIndices = 3; - // check for X3DMetadataObject childs. - if (!mReader->isEmptyElement()) - ParseNode_Metadata(ne, "Cone"); - else - NodeElement_Cur->Child.push_back(ne); // add made object as child to current element - - NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph - } // if(!use.empty()) else -} - -// -void X3DImporter::ParseNode_Geometry3D_Cylinder() { - std::string use, def; - bool bottom = true; - float height = 2; - float radius = 1; - bool side = true; - bool solid = true; - bool top = true; - CX3DImporter_NodeElement *ne(nullptr); - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_RET("radius", radius, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("bottom", bottom, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("top", top, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("side", side, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("height", height, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(def, use, ENET_Cylinder, ne); - } else { - const unsigned int tess = 30; ///TODO: IME tessellation factor through ai_property - - std::vector tside; // temp array for vertices of side. - std::vector tcir; // temp array for vertices of circle. - - // create and if needed - define new geometry object. - ne = new CX3DImporter_NodeElement_Geometry3D(CX3DImporter_NodeElement::ENET_Cylinder, NodeElement_Cur); - if (!def.empty()) ne->ID = def; - - // make cilynder or parts according to flags. - if (side) StandardShapes::MakeCone(height, radius, radius, tess, tside, true); - - height /= 2; // height defined for whole cylinder, when creating top and bottom circle we are using just half of height. - if (top || bottom) StandardShapes::MakeCircle(radius, tess, tcir); - // copy data from temp arrays - std::list &vlist = ((CX3DImporter_NodeElement_Geometry3D *)ne)->Vertices; // just short alias. - - for (std::vector::iterator it = tside.begin(); it != tside.end(); ++it) - vlist.push_back(*it); - - if (top) { - for (std::vector::iterator it = tcir.begin(); it != tcir.end(); ++it) { - (*it).y = height; // y - because circle made in oXZ. - vlist.push_back(*it); - } - } // if(top) - - if (bottom) { - for (std::vector::iterator it = tcir.begin(); it != tcir.end(); ++it) { - (*it).y = -height; // y - because circle made in oXZ. - vlist.push_back(*it); - } - } // if(top) - - ((CX3DImporter_NodeElement_Geometry3D *)ne)->Solid = solid; - ((CX3DImporter_NodeElement_Geometry3D *)ne)->NumIndices = 3; - // check for X3DMetadataObject childs. - if (!mReader->isEmptyElement()) - ParseNode_Metadata(ne, "Cylinder"); - else - NodeElement_Cur->Child.push_back(ne); // add made object as child to current element - - NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph - } // if(!use.empty()) else -} - -// -// -// ColorNormalTexCoordContentModel can contain Color (or ColorRGBA), Normal and TextureCoordinate, in any order. No more than one instance of any single -// node type is allowed. A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. -// -// The ElevationGrid node specifies a uniform rectangular grid of varying height in the Y=0 plane of the local coordinate system. The geometry is described -// by a scalar array of height values that specify the height of a surface above each point of the grid. The xDimension and zDimension fields indicate -// the number of elements of the grid height array in the X and Z directions. Both xDimension and zDimension shall be greater than or equal to zero. -// If either the xDimension or the zDimension is less than two, the ElevationGrid contains no quadrilaterals. -void X3DImporter::ParseNode_Geometry3D_ElevationGrid() { - std::string use, def; - bool ccw = true; - bool colorPerVertex = true; - float creaseAngle = 0; - std::vector height; - bool normalPerVertex = true; - bool solid = true; - int32_t xDimension = 0; - float xSpacing = 1; - int32_t zDimension = 0; - float zSpacing = 1; - CX3DImporter_NodeElement *ne(nullptr); - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("ccw", ccw, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("colorPerVertex", colorPerVertex, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("normalPerVertex", normalPerVertex, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("creaseAngle", creaseAngle, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_REF("height", height, XML_ReadNode_GetAttrVal_AsArrF); - MACRO_ATTRREAD_CHECK_RET("xDimension", xDimension, XML_ReadNode_GetAttrVal_AsI32); - MACRO_ATTRREAD_CHECK_RET("xSpacing", xSpacing, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_RET("zDimension", zDimension, XML_ReadNode_GetAttrVal_AsI32); - MACRO_ATTRREAD_CHECK_RET("zSpacing", zSpacing, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(def, use, ENET_ElevationGrid, ne); - } else { - if ((xSpacing == 0.0f) || (zSpacing == 0.0f)) throw DeadlyImportError("Spacing in must be grater than zero."); - if ((xDimension <= 0) || (zDimension <= 0)) throw DeadlyImportError("Dimension in must be grater than zero."); - if ((size_t)(xDimension * zDimension) != height.size()) Throw_IncorrectAttrValue("Heights count must be equal to \"xDimension * zDimension\""); - - // create and if needed - define new geometry object. - ne = new CX3DImporter_NodeElement_ElevationGrid(CX3DImporter_NodeElement::ENET_ElevationGrid, NodeElement_Cur); - if (!def.empty()) ne->ID = def; - - CX3DImporter_NodeElement_ElevationGrid &grid_alias = *((CX3DImporter_NodeElement_ElevationGrid *)ne); // create alias for conveience - - { // create grid vertices list - std::vector::const_iterator he_it = height.begin(); - - for (int32_t zi = 0; zi < zDimension; zi++) // rows - { - for (int32_t xi = 0; xi < xDimension; xi++) // columns - { - aiVector3D tvec(xSpacing * xi, *he_it, zSpacing * zi); - - grid_alias.Vertices.push_back(tvec); - ++he_it; - } - } - } // END: create grid vertices list - // - // create faces list. In "coordIdx" format - // - // check if we have quads - if ((xDimension < 2) || (zDimension < 2)) // only one element in dimension is set, create line set. - { - ((CX3DImporter_NodeElement_ElevationGrid *)ne)->NumIndices = 2; // will be holded as line set. - for (size_t i = 0, i_e = (grid_alias.Vertices.size() - 1); i < i_e; i++) { - grid_alias.CoordIdx.push_back(static_cast(i)); - grid_alias.CoordIdx.push_back(static_cast(i + 1)); - grid_alias.CoordIdx.push_back(-1); - } - } else // two or more elements in every dimension is set. create quad set. - { - ((CX3DImporter_NodeElement_ElevationGrid *)ne)->NumIndices = 4; - for (int32_t fzi = 0, fzi_e = (zDimension - 1); fzi < fzi_e; fzi++) // rows - { - for (int32_t fxi = 0, fxi_e = (xDimension - 1); fxi < fxi_e; fxi++) // columns - { - // points direction in face. - if (ccw) { - // CCW: - // 3 2 - // 0 1 - grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + fxi); - grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + (fxi + 1)); - grid_alias.CoordIdx.push_back(fzi * xDimension + (fxi + 1)); - grid_alias.CoordIdx.push_back(fzi * xDimension + fxi); - } else { - // CW: - // 0 1 - // 3 2 - grid_alias.CoordIdx.push_back(fzi * xDimension + fxi); - grid_alias.CoordIdx.push_back(fzi * xDimension + (fxi + 1)); - grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + (fxi + 1)); - grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + fxi); - } // if(ccw) else - - grid_alias.CoordIdx.push_back(-1); - } // for(int32_t fxi = 0, fxi_e = (xDimension - 1); fxi < fxi_e; fxi++) - } // for(int32_t fzi = 0, fzi_e = (zDimension - 1); fzi < fzi_e; fzi++) - } // if((xDimension < 2) || (zDimension < 2)) else - - grid_alias.ColorPerVertex = colorPerVertex; - grid_alias.NormalPerVertex = normalPerVertex; - grid_alias.CreaseAngle = creaseAngle; - grid_alias.Solid = solid; - // check for child nodes - if (!mReader->isEmptyElement()) { - ParseHelper_Node_Enter(ne); - MACRO_NODECHECK_LOOPBEGIN("ElevationGrid"); - // check for X3DComposedGeometryNodes - if (XML_CheckNode_NameEqual("Color")) { - ParseNode_Rendering_Color(); - continue; - } - if (XML_CheckNode_NameEqual("ColorRGBA")) { - ParseNode_Rendering_ColorRGBA(); - continue; - } - if (XML_CheckNode_NameEqual("Normal")) { - ParseNode_Rendering_Normal(); - continue; - } - if (XML_CheckNode_NameEqual("TextureCoordinate")) { - ParseNode_Texturing_TextureCoordinate(); - continue; - } - // check for X3DMetadataObject - if (!ParseHelper_CheckRead_X3DMetadataObject()) XML_CheckNode_SkipUnsupported("ElevationGrid"); - - MACRO_NODECHECK_LOOPEND("ElevationGrid"); - ParseHelper_Node_Exit(); - } // if(!mReader->isEmptyElement()) - else { - NodeElement_Cur->Child.push_back(ne); // add made object as child to current element - } // if(!mReader->isEmptyElement()) else - - NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph - } // if(!use.empty()) else -} - -template -static void GeometryHelper_Extrusion_CurveIsClosed(std::vector &pCurve, const bool pDropTail, const bool pRemoveLastPoint, bool &pCurveIsClosed) { - size_t cur_sz = pCurve.size(); - - pCurveIsClosed = false; - // for curve with less than four points checking is have no sense, - if (cur_sz < 4) return; - - for (size_t s = 3, s_e = cur_sz; s < s_e; s++) { - // search for first point of duplicated part. - if (pCurve[0] == pCurve[s]) { - bool found = true; - - // check if tail(indexed by b2) is duplicate of head(indexed by b1). - for (size_t b1 = 1, b2 = (s + 1); b2 < cur_sz; b1++, b2++) { - if (pCurve[b1] != pCurve[b2]) { // points not match: clear flag and break loop. - found = false; - - break; - } - } // for(size_t b1 = 1, b2 = (s + 1); b2 < cur_sz; b1++, b2++) - - // if duplicate tail is found then drop or not it depending on flags. - if (found) { - pCurveIsClosed = true; - if (pDropTail) { - if (!pRemoveLastPoint) s++; // prepare value for iterator's arithmetics. - - pCurve.erase(pCurve.begin() + s, pCurve.end()); // remove tail - } - - break; - } // if(found) - } // if(pCurve[0] == pCurve[s]) - } // for(size_t s = 3, s_e = (cur_sz - 1); s < s_e; s++) -} - -static aiVector3D GeometryHelper_Extrusion_GetNextY(const size_t pSpine_PointIdx, const std::vector &pSpine, const bool pSpine_Closed) { - const size_t spine_idx_last = pSpine.size() - 1; - aiVector3D tvec; - - if ((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last)) // at first special cases - { - if (pSpine_Closed) { // If the spine curve is closed: The SCP for the first and last points is the same and is found using (spine[1] - spine[n - 2]) to compute the Y-axis. - // As we even for closed spine curve last and first point in pSpine are not the same: duplicates(spine[n - 1] which are equivalent to spine[0]) - // in tail are removed. - // So, last point in pSpine is a spine[n - 2] - tvec = pSpine[1] - pSpine[spine_idx_last]; - } else if (pSpine_PointIdx == 0) { // The Y-axis used for the first point is the vector from spine[0] to spine[1] - tvec = pSpine[1] - pSpine[0]; - } else { // The Y-axis used for the last point it is the vector from spine[n-2] to spine[n-1]. In our case(see above about dropping tail) spine[n - 1] is - // the spine[0]. - tvec = pSpine[spine_idx_last] - pSpine[spine_idx_last - 1]; - } - } // if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last)) - else { // For all points other than the first or last: The Y-axis for spine[i] is found by normalizing the vector defined by (spine[i+1] - spine[i-1]). - tvec = pSpine[pSpine_PointIdx + 1] - pSpine[pSpine_PointIdx - 1]; - } // if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last)) else - - return tvec.Normalize(); -} - -static aiVector3D GeometryHelper_Extrusion_GetNextZ(const size_t pSpine_PointIdx, const std::vector &pSpine, const bool pSpine_Closed, - const aiVector3D pVecZ_Prev) { - const aiVector3D zero_vec(0); - const size_t spine_idx_last = pSpine.size() - 1; - - aiVector3D tvec; - - // at first special cases - if (pSpine.size() < 3) // spine have not enough points for vector calculations. - { - tvec.Set(0, 0, 1); - } else if (pSpine_PointIdx == 0) // special case: first point - { - if (pSpine_Closed) // for calculating use previous point in curve s[n - 2]. In list it's a last point, because point s[n - 1] was removed as duplicate. - { - tvec = (pSpine[1] - pSpine[0]) ^ (pSpine[spine_idx_last] - pSpine[0]); - } else // for not closed curve first and next point(s[0] and s[1]) has the same vector Z. - { - bool found = false; - - // As said: "If the Z-axis of the first point is undefined (because the spine is not closed and the first two spine segments are collinear) - // then the Z-axis for the first spine point with a defined Z-axis is used." - // Walk through spine and find Z. - for (size_t next_point = 2; (next_point <= spine_idx_last) && !found; next_point++) { - // (pSpine[2] - pSpine[1]) ^ (pSpine[0] - pSpine[1]) - tvec = (pSpine[next_point] - pSpine[next_point - 1]) ^ (pSpine[next_point - 2] - pSpine[next_point - 1]); - found = !tvec.Equal(zero_vec); - } - - // if entire spine are collinear then use OZ axis. - if (!found) tvec.Set(0, 0, 1); - } // if(pSpine_Closed) else - } // else if(pSpine_PointIdx == 0) - else if (pSpine_PointIdx == spine_idx_last) // special case: last point - { - if (pSpine_Closed) { // do not forget that real last point s[n - 1] is removed as duplicated. And in this case we are calculating vector Z for point s[n - 2]. - tvec = (pSpine[0] - pSpine[pSpine_PointIdx]) ^ (pSpine[pSpine_PointIdx - 1] - pSpine[pSpine_PointIdx]); - // if taken spine vectors are collinear then use previous vector Z. - if (tvec.Equal(zero_vec)) tvec = pVecZ_Prev; - } else { // vector Z for last point of not closed curve is previous vector Z. - tvec = pVecZ_Prev; - } - } else // regular point - { - tvec = (pSpine[pSpine_PointIdx + 1] - pSpine[pSpine_PointIdx]) ^ (pSpine[pSpine_PointIdx - 1] - pSpine[pSpine_PointIdx]); - // if taken spine vectors are collinear then use previous vector Z. - if (tvec.Equal(zero_vec)) tvec = pVecZ_Prev; - } - - // After determining the Z-axis, its dot product with the Z-axis of the previous spine point is computed. If this value is negative, the Z-axis - // is flipped (multiplied by -1). - if ((tvec * pVecZ_Prev) < 0) tvec = -tvec; - - return tvec.Normalize(); -} - -// -void X3DImporter::ParseNode_Geometry3D_Extrusion() { - std::string use, def; - bool beginCap = true; - bool ccw = true; - bool convex = true; - float creaseAngle = 0; - std::vector crossSection; - bool endCap = true; - std::vector orientation; - std::vector scale; - bool solid = true; - std::vector spine; - CX3DImporter_NodeElement *ne(nullptr); - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_RET("beginCap", beginCap, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("ccw", ccw, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("convex", convex, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("creaseAngle", creaseAngle, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_REF("crossSection", crossSection, XML_ReadNode_GetAttrVal_AsArrVec2f); - MACRO_ATTRREAD_CHECK_RET("endCap", endCap, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_REF("orientation", orientation, XML_ReadNode_GetAttrVal_AsArrF); - MACRO_ATTRREAD_CHECK_REF("scale", scale, XML_ReadNode_GetAttrVal_AsArrVec2f); - MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_REF("spine", spine, XML_ReadNode_GetAttrVal_AsArrVec3f); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(def, use, ENET_Extrusion, ne); - } else { - // - // check if default values must be assigned - // - if (spine.size() == 0) { - spine.resize(2); - spine[0].Set(0, 0, 0), spine[1].Set(0, 1, 0); - } else if (spine.size() == 1) { - throw DeadlyImportError("ParseNode_Geometry3D_Extrusion. Spine must have at least two points."); - } - - if (crossSection.size() == 0) { - crossSection.resize(5); - crossSection[0].Set(1, 1), crossSection[1].Set(1, -1), crossSection[2].Set(-1, -1), crossSection[3].Set(-1, 1), crossSection[4].Set(1, 1); - } - - { // orientation - size_t ori_size = orientation.size() / 4; - - if (ori_size < spine.size()) { - float add_ori[4]; // values that will be added - - if (ori_size == 1) // if "orientation" has one element(means one MFRotation with four components) then use it value for all spine points. - { - add_ori[0] = orientation[0], add_ori[1] = orientation[1], add_ori[2] = orientation[2], add_ori[3] = orientation[3]; - } else // else - use default values - { - add_ori[0] = 0, add_ori[1] = 0, add_ori[2] = 1, add_ori[3] = 0; - } - - orientation.reserve(spine.size() * 4); - for (size_t i = 0, i_e = (spine.size() - ori_size); i < i_e; i++) - orientation.push_back(add_ori[0]), orientation.push_back(add_ori[1]), orientation.push_back(add_ori[2]), orientation.push_back(add_ori[3]); - } - - if (orientation.size() % 4) throw DeadlyImportError("Attribute \"orientation\" in must has multiple four quantity of numbers."); - } // END: orientation - - { // scale - if (scale.size() < spine.size()) { - aiVector2D add_sc; - - if (scale.size() == 1) // if "scale" has one element then use it value for all spine points. - add_sc = scale[0]; - else // else - use default values - add_sc.Set(1, 1); - - scale.reserve(spine.size()); - for (size_t i = 0, i_e = (spine.size() - scale.size()); i < i_e; i++) - scale.push_back(add_sc); - } - } // END: scale - // - // create and if needed - define new geometry object. - // - ne = new CX3DImporter_NodeElement_IndexedSet(CX3DImporter_NodeElement::ENET_Extrusion, NodeElement_Cur); - if (!def.empty()) ne->ID = def; - - CX3DImporter_NodeElement_IndexedSet &ext_alias = *((CX3DImporter_NodeElement_IndexedSet *)ne); // create alias for conveience - // assign part of input data - ext_alias.CCW = ccw; - ext_alias.Convex = convex; - ext_alias.CreaseAngle = creaseAngle; - ext_alias.Solid = solid; - - // - // How we done it at all? - // 1. At first we will calculate array of basises for every point in spine(look SCP in ISO-dic). Also "orientation" vector - // are applied vor every basis. - // 2. After that we can create array of point sets: which are scaled, transferred to basis of relative basis and at final translated to real position - // using relative spine point. - // 3. Next step is creating CoordIdx array(do not forget "-1" delimiter). While creating CoordIdx also created faces for begin and end caps, if - // needed. While createing CootdIdx is taking in account CCW flag. - // 4. The last step: create Vertices list. - // - bool spine_closed; // flag: true if spine curve is closed. - bool cross_closed; // flag: true if cross curve is closed. - std::vector basis_arr; // array of basises. ROW_a - X, ROW_b - Y, ROW_c - Z. - std::vector> pointset_arr; // array of point sets: cross curves. - - // detect closed curves - GeometryHelper_Extrusion_CurveIsClosed(crossSection, true, true, cross_closed); // true - drop tail, true - remove duplicate end. - GeometryHelper_Extrusion_CurveIsClosed(spine, true, true, spine_closed); // true - drop tail, true - remove duplicate end. - // If both cap are requested and spine curve is closed then we can make only one cap. Because second cap will be the same surface. - if (spine_closed) { - beginCap |= endCap; - endCap = false; - } - - { // 1. Calculate array of basises. - aiMatrix4x4 rotmat; - aiVector3D vecX(0), vecY(0), vecZ(0); - - basis_arr.resize(spine.size()); - for (size_t i = 0, i_e = spine.size(); i < i_e; i++) { - aiVector3D tvec; - - // get axises of basis. - vecY = GeometryHelper_Extrusion_GetNextY(i, spine, spine_closed); - vecZ = GeometryHelper_Extrusion_GetNextZ(i, spine, spine_closed, vecZ); - vecX = (vecY ^ vecZ).Normalize(); - // get rotation matrix and apply "orientation" to basis - aiMatrix4x4::Rotation(orientation[i * 4 + 3], aiVector3D(orientation[i * 4], orientation[i * 4 + 1], orientation[i * 4 + 2]), rotmat); - tvec = vecX, tvec *= rotmat, basis_arr[i].a1 = tvec.x, basis_arr[i].a2 = tvec.y, basis_arr[i].a3 = tvec.z; - tvec = vecY, tvec *= rotmat, basis_arr[i].b1 = tvec.x, basis_arr[i].b2 = tvec.y, basis_arr[i].b3 = tvec.z; - tvec = vecZ, tvec *= rotmat, basis_arr[i].c1 = tvec.x, basis_arr[i].c2 = tvec.y, basis_arr[i].c3 = tvec.z; - } // for(size_t i = 0, i_e = spine.size(); i < i_e; i++) - } // END: 1. Calculate array of basises - - { // 2. Create array of point sets. - aiMatrix4x4 scmat; - std::vector tcross(crossSection.size()); - - pointset_arr.resize(spine.size()); - for (size_t spi = 0, spi_e = spine.size(); spi < spi_e; spi++) { - aiVector3D tc23vec; - - tc23vec.Set(scale[spi].x, 0, scale[spi].y); - aiMatrix4x4::Scaling(tc23vec, scmat); - for (size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; cri++) { - aiVector3D tvecX, tvecY, tvecZ; - - tc23vec.Set(crossSection[cri].x, 0, crossSection[cri].y); - // apply scaling to point - tcross[cri] = scmat * tc23vec; - // - // transfer point to new basis - // calculate coordinate in new basis - tvecX.Set(basis_arr[spi].a1, basis_arr[spi].a2, basis_arr[spi].a3), tvecX *= tcross[cri].x; - tvecY.Set(basis_arr[spi].b1, basis_arr[spi].b2, basis_arr[spi].b3), tvecY *= tcross[cri].y; - tvecZ.Set(basis_arr[spi].c1, basis_arr[spi].c2, basis_arr[spi].c3), tvecZ *= tcross[cri].z; - // apply new coordinates and translate it to spine point. - tcross[cri] = tvecX + tvecY + tvecZ + spine[spi]; - } // for(size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; i++) - - pointset_arr[spi] = tcross; // store transferred point set - } // for(size_t spi = 0, spi_e = spine.size(); spi < spi_e; i++) - } // END: 2. Create array of point sets. - - { // 3. Create CoordIdx. - // add caps if needed - if (beginCap) { - // add cap as polygon. vertices of cap are places at begin, so just add numbers from zero. - for (size_t i = 0, i_e = crossSection.size(); i < i_e; i++) - ext_alias.CoordIndex.push_back(static_cast(i)); - - // add delimiter - ext_alias.CoordIndex.push_back(-1); - } // if(beginCap) - - if (endCap) { - // add cap as polygon. vertices of cap are places at end, as for beginCap use just sequence of numbers but with offset. - size_t beg = (pointset_arr.size() - 1) * crossSection.size(); - - for (size_t i = beg, i_e = (beg + crossSection.size()); i < i_e; i++) - ext_alias.CoordIndex.push_back(static_cast(i)); - - // add delimiter - ext_alias.CoordIndex.push_back(-1); - } // if(beginCap) - - // add quads - for (size_t spi = 0, spi_e = (spine.size() - 1); spi <= spi_e; spi++) { - const size_t cr_sz = crossSection.size(); - const size_t cr_last = crossSection.size() - 1; - - size_t right_col; // hold index basis for points of quad placed in right column; - - if (spi != spi_e) - right_col = spi + 1; - else if (spine_closed) // if spine curve is closed then one more quad is needed: between first and last points of curve. - right_col = 0; - else - break; // if spine curve is not closed then break the loop, because spi is out of range for that type of spine. - - for (size_t cri = 0; cri < cr_sz; cri++) { - if (cri != cr_last) { - MACRO_FACE_ADD_QUAD(ccw, ext_alias.CoordIndex, - static_cast(spi * cr_sz + cri), - static_cast(right_col * cr_sz + cri), - static_cast(right_col * cr_sz + cri + 1), - static_cast(spi * cr_sz + cri + 1)); - // add delimiter - ext_alias.CoordIndex.push_back(-1); - } else if (cross_closed) // if cross curve is closed then one more quad is needed: between first and last points of curve. - { - MACRO_FACE_ADD_QUAD(ccw, ext_alias.CoordIndex, - static_cast(spi * cr_sz + cri), - static_cast(right_col * cr_sz + cri), - static_cast(right_col * cr_sz + 0), - static_cast(spi * cr_sz + 0)); - // add delimiter - ext_alias.CoordIndex.push_back(-1); - } - } // for(size_t cri = 0; cri < cr_sz; cri++) - } // for(size_t spi = 0, spi_e = (spine.size() - 2); spi < spi_e; spi++) - } // END: 3. Create CoordIdx. - - { // 4. Create vertices list. - // just copy all vertices - for (size_t spi = 0, spi_e = spine.size(); spi < spi_e; spi++) { - for (size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; cri++) { - ext_alias.Vertices.push_back(pointset_arr[spi][cri]); - } - } - } // END: 4. Create vertices list. - //PrintVectorSet("Ext. CoordIdx", ext_alias.CoordIndex); - //PrintVectorSet("Ext. Vertices", ext_alias.Vertices); - // check for child nodes - if (!mReader->isEmptyElement()) - ParseNode_Metadata(ne, "Extrusion"); - else - NodeElement_Cur->Child.push_back(ne); // add made object as child to current element - - NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph - } // if(!use.empty()) else -} - -// -// -// ComposedGeometryContentModel is the child-node content model corresponding to X3DComposedGeometryNodes. It can contain Color (or ColorRGBA), Coordinate, -// Normal and TextureCoordinate, in any order. No more than one instance of these nodes is allowed. Multiple VertexAttribute (FloatVertexAttribute, -// Matrix3VertexAttribute, Matrix4VertexAttribute) nodes can also be contained. -// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. -// -void X3DImporter::ParseNode_Geometry3D_IndexedFaceSet() { - std::string use, def; - bool ccw = true; - std::vector colorIndex; - bool colorPerVertex = true; - bool convex = true; - std::vector coordIndex; - float creaseAngle = 0; - std::vector normalIndex; - bool normalPerVertex = true; - bool solid = true; - std::vector texCoordIndex; - CX3DImporter_NodeElement *ne(nullptr); - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_RET("ccw", ccw, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_REF("colorIndex", colorIndex, XML_ReadNode_GetAttrVal_AsArrI32); - MACRO_ATTRREAD_CHECK_RET("colorPerVertex", colorPerVertex, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("convex", convex, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_REF("coordIndex", coordIndex, XML_ReadNode_GetAttrVal_AsArrI32); - MACRO_ATTRREAD_CHECK_RET("creaseAngle", creaseAngle, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_REF("normalIndex", normalIndex, XML_ReadNode_GetAttrVal_AsArrI32); - MACRO_ATTRREAD_CHECK_RET("normalPerVertex", normalPerVertex, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_CHECK_REF("texCoordIndex", texCoordIndex, XML_ReadNode_GetAttrVal_AsArrI32); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(def, use, ENET_IndexedFaceSet, ne); - } else { - // check data - if (coordIndex.size() == 0) throw DeadlyImportError("IndexedFaceSet must contain not empty \"coordIndex\" attribute."); - - // create and if needed - define new geometry object. - ne = new CX3DImporter_NodeElement_IndexedSet(CX3DImporter_NodeElement::ENET_IndexedFaceSet, NodeElement_Cur); - if (!def.empty()) ne->ID = def; - - CX3DImporter_NodeElement_IndexedSet &ne_alias = *((CX3DImporter_NodeElement_IndexedSet *)ne); - - ne_alias.CCW = ccw; - ne_alias.ColorIndex = colorIndex; - ne_alias.ColorPerVertex = colorPerVertex; - ne_alias.Convex = convex; - ne_alias.CoordIndex = coordIndex; - ne_alias.CreaseAngle = creaseAngle; - ne_alias.NormalIndex = normalIndex; - ne_alias.NormalPerVertex = normalPerVertex; - ne_alias.Solid = solid; - ne_alias.TexCoordIndex = texCoordIndex; - // check for child nodes - if (!mReader->isEmptyElement()) { - ParseHelper_Node_Enter(ne); - MACRO_NODECHECK_LOOPBEGIN("IndexedFaceSet"); - // check for X3DComposedGeometryNodes - if (XML_CheckNode_NameEqual("Color")) { - ParseNode_Rendering_Color(); - continue; - } - if (XML_CheckNode_NameEqual("ColorRGBA")) { - ParseNode_Rendering_ColorRGBA(); - continue; - } - if (XML_CheckNode_NameEqual("Coordinate")) { - ParseNode_Rendering_Coordinate(); - continue; - } - if (XML_CheckNode_NameEqual("Normal")) { - ParseNode_Rendering_Normal(); - continue; - } - if (XML_CheckNode_NameEqual("TextureCoordinate")) { - ParseNode_Texturing_TextureCoordinate(); - continue; - } - // check for X3DMetadataObject - if (!ParseHelper_CheckRead_X3DMetadataObject()) XML_CheckNode_SkipUnsupported("IndexedFaceSet"); - - MACRO_NODECHECK_LOOPEND("IndexedFaceSet"); - ParseHelper_Node_Exit(); - } // if(!mReader->isEmptyElement()) - else { - NodeElement_Cur->Child.push_back(ne); // add made object as child to current element - } - - NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph - } // if(!use.empty()) else -} - -// -void X3DImporter::ParseNode_Geometry3D_Sphere() { - std::string use, def; - ai_real radius = 1; - bool solid = true; - CX3DImporter_NodeElement *ne(nullptr); - - MACRO_ATTRREAD_LOOPBEG; - MACRO_ATTRREAD_CHECKUSEDEF_RET(def, use); - MACRO_ATTRREAD_CHECK_RET("radius", radius, XML_ReadNode_GetAttrVal_AsFloat); - MACRO_ATTRREAD_CHECK_RET("solid", solid, XML_ReadNode_GetAttrVal_AsBool); - MACRO_ATTRREAD_LOOPEND; - - // if "USE" defined then find already defined element. - if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(def, use, ENET_Sphere, ne); - } else { - const unsigned int tess = 3; ///TODO: IME tessellation factor through ai_property - - std::vector tlist; - - // create and if needed - define new geometry object. - ne = new CX3DImporter_NodeElement_Geometry3D(CX3DImporter_NodeElement::ENET_Sphere, NodeElement_Cur); - if (!def.empty()) ne->ID = def; - - StandardShapes::MakeSphere(tess, tlist); - // copy data from temp array and apply scale - for (std::vector::iterator it = tlist.begin(); it != tlist.end(); ++it) { - ((CX3DImporter_NodeElement_Geometry3D *)ne)->Vertices.push_back(*it * radius); - } - - ((CX3DImporter_NodeElement_Geometry3D *)ne)->Solid = solid; - ((CX3DImporter_NodeElement_Geometry3D *)ne)->NumIndices = 3; - // check for X3DMetadataObject childs. - if (!mReader->isEmptyElement()) - ParseNode_Metadata(ne, "Sphere"); - else - NodeElement_Cur->Child.push_back(ne); // add made object as child to current element - - NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph - } // if(!use.empty()) else -} - - - -void X3DImporter::readMetadataObject(XmlNode &node) { - const std::string &name = node.name(); - if (name == "MetadataBoolean") { - readMetadataBoolean(node, mNodeElementCur); - } else if (name == "MetadataDouble") { - readMetadataDouble(node, mNodeElementCur); - } else if (name == "MetadataFloat") { - readMetadataFloat(node, mNodeElementCur); - } else if (name == "MetadataInteger") { - readMetadataInteger(node, mNodeElementCur); - } else if (name == "MetadataSet") { - readMetadataSet(node, mNodeElementCur); - } else if (name == "MetadataString") { - readMetadataString(node, mNodeElementCur); - } -} - - -aiMatrix4x4 PostprocessHelper_Matrix_GlobalToCurrent() { - X3DNodeElementBase *cur_node = nullptr; - std::list matr; - aiMatrix4x4 out_matr; - - // starting walk from current element to root - cur_node = cur_node; - if (cur_node != nullptr) { - do { - // if cur_node is group then store group transformation matrix in list. - if (cur_node->Type == X3DNodeElementBase::ENET_Group) matr.push_back(((X3DNodeElementBase *)cur_node)->Transformation); - - cur_node = cur_node->Parent; - } while (cur_node != nullptr); - } - - // multiplicate all matrices in reverse order - for (std::list::reverse_iterator rit = matr.rbegin(); rit != matr.rend(); ++rit) - out_matr = out_matr * (*rit); - - return out_matr; -} - -void X3DImporter::PostprocessHelper_CollectMetadata(const CX3DImporter_NodeElement &pNodeElement, std::list &pList) const { - // walk through childs and find for metadata. - for (std::list::const_iterator el_it = pNodeElement.Child.begin(); el_it != pNodeElement.Child.end(); ++el_it) { - if (((*el_it)->Type == X3DElemType::ENET_MetaBoolean) || ((*el_it)->Type == X3DElemType::ENET_MetaDouble) || - ((*el_it)->Type == X3DElemType::ENET_MetaFloat) || ((*el_it)->Type == X3DElemType::ENET_MetaInteger) || - ((*el_it)->Type == X3DElemType::ENET_MetaString)) { - pList.push_back(*el_it); - } else if ((*el_it)->Type == X3DElemType::ENET_MetaSet) { - PostprocessHelper_CollectMetadata(**el_it, pList); - } - } // for(std::list::const_iterator el_it = pNodeElement.Child.begin(); el_it != pNodeElement.Child.end(); el_it++) -} - -bool X3DImporter::PostprocessHelper_ElementIsMetadata(const CX3DImporter_NodeElement::EType pType) const { - if ((pType == X3DNodeElementBase::ENET_MetaBoolean) || (pType == X3DElemType::ENET_MetaDouble) || - (pType == X3DElemType::ENET_MetaFloat) || (pType == X3DElemType::ENET_MetaInteger) || - (pType == X3DElemType::ENET_MetaString) || (pType == X3DElemType::ENET_MetaSet)) { - return true; - } return false; } -bool X3DImporter::PostprocessHelper_ElementIsMesh(const CX3DImporter_NodeElement::EType pType) const { - if ((pType == X3DElemType::ENET_Arc2D) || (pType == X3DElemType::ENET_ArcClose2D) || - (pType == X3DElemType::ENET_Box) || (pType == X3DElemType::ENET_Circle2D) || - (pType == X3DElemType::ENET_Cone) || (pType == X3DElemType::ENET_Cylinder) || - (pType == X3DElemType::ENET_Disk2D) || (pType == X3DElemType::ENET_ElevationGrid) || - (pType == X3DElemType::ENET_Extrusion) || (pType == X3DElemType::ENET_IndexedFaceSet) || - (pType == X3DElemType::ENET_IndexedLineSet) || (pType == X3DElemType::ENET_IndexedTriangleFanSet) || - (pType == X3DElemType::ENET_IndexedTriangleSet) || (pType == X3DElemType::ENET_IndexedTriangleStripSet) || - (pType == X3DElemType::ENET_PointSet) || (pType == X3DElemType::ENET_LineSet) || - (pType == X3DElemType::ENET_Polyline2D) || (pType == X3DElemType::ENET_Polypoint2D) || - (pType == X3DElemType::ENET_Rectangle2D) || (pType == X3DElemType::ENET_Sphere) || - (pType == X3DElemType::ENET_TriangleFanSet) || (pType == X3DElemType::ENET_TriangleSet) || - (pType == X3DElemType::ENET_TriangleSet2D) || (pType == X3DElemType::ENET_TriangleStripSet)) { - return true; +bool X3DImporter::FindNodeElement_FromNode(X3DNodeElementBase *pStartNode, const std::string &pID, + const X3DElemType pType, X3DNodeElementBase **pElement) { + bool found = false; // flag: true - if requested element is found. + + // Check if pStartNode - this is the element, we are looking for. + if ((pStartNode->Type == pType) && (pStartNode->ID == pID)) { + found = true; + if (pElement != nullptr) { + *pElement = pStartNode; + } + + goto fne_fn_end; + } // if((pStartNode->Type() == pType) && (pStartNode->ID() == pID)) + + // Check childs of pStartNode. + for (std::list::iterator ch_it = pStartNode->Children.begin(); ch_it != pStartNode->Children.end(); ++ch_it) { + found = FindNodeElement_FromNode(*ch_it, pID, pType, pElement); + if (found) { + break; + } + } // for(std::list::iterator ch_it = it->Children.begin(); ch_it != it->Children.end(); ch_it++) + +fne_fn_end: + + return found; +} + +bool X3DImporter::FindNodeElement(const std::string &pID, const X3DElemType pType, X3DNodeElementBase **pElement) { + X3DNodeElementBase *tnd = mNodeElementCur; // temporary pointer to node. + bool static_search = false; // flag: true if searching in static node. + + // At first check if we have deal with static node. Go up through parent nodes and check flag. + while (tnd != nullptr) { + if (tnd->Type == X3DElemType::ENET_Group) { + if (((X3DNodeElementGroup *)tnd)->Static) { + static_search = true; // Flag found, stop walking up. Node with static flag will holded in tnd variable. + break; + } + } + + tnd = tnd->Parent; // go up in graph. + } // while (tnd != nullptr) + + // at now call appropriate search function. + if (static_search) { + return FindNodeElement_FromNode(tnd, pID, pType, pElement); } else { - return false; + return FindNodeElement_FromRoot(pID, pType, pElement); } } -void X3DImporter::Postprocess_BuildLight(const CX3DImporter_NodeElement &pNodeElement, std::list &pSceneLightList) const { - const CX3DImporter_NodeElement_Light &ne = *((CX3DImporter_NodeElement_Light *)&pNodeElement); - aiMatrix4x4 transform_matr = PostprocessHelper_Matrix_GlobalToCurrent(); - aiLight *new_light = new aiLight; +/*********************************************************************************************************************************************/ +/************************************************************ Functions: parse set ***********************************************************/ +/*********************************************************************************************************************************************/ - new_light->mName = ne.ID; - new_light->mColorAmbient = ne.Color * ne.AmbientIntensity; - new_light->mColorDiffuse = ne.Color * ne.Intensity; - new_light->mColorSpecular = ne.Color * ne.Intensity; - switch (pNodeElement.Type) { - case CX3DImporter_NodeElement::ENET_DirectionalLight: - new_light->mType = aiLightSource_DIRECTIONAL; - new_light->mDirection = ne.Direction, new_light->mDirection *= transform_matr; +void X3DImporter::ParseHelper_Group_Begin(const bool pStatic) { + X3DNodeElementGroup *new_group = new X3DNodeElementGroup(mNodeElementCur, pStatic); // create new node with current node as parent. - break; - case CX3DImporter_NodeElement::ENET_PointLight: - new_light->mType = aiLightSource_POINT; - new_light->mPosition = ne.Location, new_light->mPosition *= transform_matr; - new_light->mAttenuationConstant = ne.Attenuation.x; - new_light->mAttenuationLinear = ne.Attenuation.y; - new_light->mAttenuationQuadratic = ne.Attenuation.z; - - break; - case CX3DImporter_NodeElement::ENET_SpotLight: - new_light->mType = aiLightSource_SPOT; - new_light->mPosition = ne.Location, new_light->mPosition *= transform_matr; - new_light->mDirection = ne.Direction, new_light->mDirection *= transform_matr; - new_light->mAttenuationConstant = ne.Attenuation.x; - new_light->mAttenuationLinear = ne.Attenuation.y; - new_light->mAttenuationQuadratic = ne.Attenuation.z; - new_light->mAngleInnerCone = ne.BeamWidth; - new_light->mAngleOuterCone = ne.CutOffAngle; - - break; - default: - throw DeadlyImportError("Postprocess_BuildLight. Unknown type of light: " + to_string(pNodeElement.Type) + "."); + // if we are adding not the root element then add new element to current element child list. + if (mNodeElementCur != nullptr) { + mNodeElementCur->Children.push_back(new_group); } - pSceneLightList.push_back(new_light); + NodeElement_List.push_back(new_group); // it's a new element - add it to list. + mNodeElementCur = new_group; // switch current element to new one. } -void X3DImporter::Postprocess_BuildMaterial(const CX3DImporter_NodeElement &pNodeElement, aiMaterial **pMaterial) const { - // check argument - if (pMaterial == nullptr) throw DeadlyImportError("Postprocess_BuildMaterial. pMaterial is nullptr."); - if (*pMaterial != nullptr) throw DeadlyImportError("Postprocess_BuildMaterial. *pMaterial must be nullptr."); +void X3DImporter::ParseHelper_Node_Enter(X3DNodeElementBase *pNode) { + mNodeElementCur->Children.push_back(pNode); // add new element to current element child list. + mNodeElementCur = pNode; // switch current element to new one. +} - *pMaterial = new aiMaterial; - aiMaterial &taimat = **pMaterial; // creating alias for convenience. - - // at this point pNodeElement point to node. Walk through childs and add all stored data. - for (std::list::const_iterator el_it = pNodeElement.Child.begin(); el_it != pNodeElement.Child.end(); ++el_it) { - if ((*el_it)->Type == CX3DImporter_NodeElement::ENET_Material) { - aiColor3D tcol3; - float tvalf; - CX3DImporter_NodeElement_Material &tnemat = *((CX3DImporter_NodeElement_Material *)*el_it); - - tcol3.r = tnemat.AmbientIntensity, tcol3.g = tnemat.AmbientIntensity, tcol3.b = tnemat.AmbientIntensity; - taimat.AddProperty(&tcol3, 1, AI_MATKEY_COLOR_AMBIENT); - taimat.AddProperty(&tnemat.DiffuseColor, 1, AI_MATKEY_COLOR_DIFFUSE); - taimat.AddProperty(&tnemat.EmissiveColor, 1, AI_MATKEY_COLOR_EMISSIVE); - taimat.AddProperty(&tnemat.SpecularColor, 1, AI_MATKEY_COLOR_SPECULAR); - tvalf = 1; - taimat.AddProperty(&tvalf, 1, AI_MATKEY_SHININESS_STRENGTH); - taimat.AddProperty(&tnemat.Shininess, 1, AI_MATKEY_SHININESS); - tvalf = 1.0f - tnemat.Transparency; - taimat.AddProperty(&tvalf, 1, AI_MATKEY_OPACITY); - } // if((*el_it)->Type == CX3DImporter_NodeElement::ENET_Material) - else if ((*el_it)->Type == CX3DImporter_NodeElement::ENET_ImageTexture) { - CX3DImporter_NodeElement_ImageTexture &tnetex = *((CX3DImporter_NodeElement_ImageTexture *)*el_it); - aiString url_str(tnetex.URL.c_str()); - int mode = aiTextureOp_Multiply; - - taimat.AddProperty(&url_str, AI_MATKEY_TEXTURE_DIFFUSE(0)); - taimat.AddProperty(&tnetex.RepeatS, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0)); - taimat.AddProperty(&tnetex.RepeatT, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0)); - taimat.AddProperty(&mode, 1, AI_MATKEY_TEXOP_DIFFUSE(0)); - } // else if((*el_it)->Type == CX3DImporter_NodeElement::ENET_ImageTexture) - else if ((*el_it)->Type == CX3DImporter_NodeElement::ENET_TextureTransform) { - aiUVTransform trans; - CX3DImporter_NodeElement_TextureTransform &tnetextr = *((CX3DImporter_NodeElement_TextureTransform *)*el_it); - - trans.mTranslation = tnetextr.Translation - tnetextr.Center; - trans.mScaling = tnetextr.Scale; - trans.mRotation = tnetextr.Rotation; - taimat.AddProperty(&trans, 1, AI_MATKEY_UVTRANSFORM_DIFFUSE(0)); - } // else if((*el_it)->Type == CX3DImporter_NodeElement::ENET_TextureTransform) - } // for(std::list::const_iterator el_it = pNodeElement.Child.begin(); el_it != pNodeElement.Child.end(); el_it++) -} - -void X3DImporter::Postprocess_BuildMesh(const CX3DImporter_NodeElement &pNodeElement, aiMesh **pMesh) const { - // check argument - if (pMesh == nullptr) throw DeadlyImportError("Postprocess_BuildMesh. pMesh is nullptr."); - if (*pMesh != nullptr) throw DeadlyImportError("Postprocess_BuildMesh. *pMesh must be nullptr."); - - /************************************************************************************************************************************/ - /************************************************************ Geometry2D ************************************************************/ - /************************************************************************************************************************************/ - if ((pNodeElement.Type == CX3DImporter_NodeElement::ENET_Arc2D) || (pNodeElement.Type == CX3DImporter_NodeElement::ENET_ArcClose2D) || - (pNodeElement.Type == CX3DImporter_NodeElement::ENET_Circle2D) || (pNodeElement.Type == CX3DImporter_NodeElement::ENET_Disk2D) || - (pNodeElement.Type == CX3DImporter_NodeElement::ENET_Polyline2D) || (pNodeElement.Type == CX3DImporter_NodeElement::ENET_Polypoint2D) || - (pNodeElement.Type == CX3DImporter_NodeElement::ENET_Rectangle2D) || (pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleSet2D)) { - CX3DImporter_NodeElement_Geometry2D &tnemesh = *((CX3DImporter_NodeElement_Geometry2D *)&pNodeElement); // create alias for convenience - std::vector tarr; - - tarr.reserve(tnemesh.Vertices.size()); - for (std::list::iterator it = tnemesh.Vertices.begin(); it != tnemesh.Vertices.end(); ++it) - tarr.push_back(*it); - *pMesh = StandardShapes::MakeMesh(tarr, static_cast(tnemesh.NumIndices)); // create mesh from vertices using Assimp help. - - return; // mesh is build, nothing to do anymore. +void X3DImporter::ParseHelper_Node_Exit() { + // check if we can walk up. + if (mNodeElementCur != nullptr) { + mNodeElementCur = mNodeElementCur->Parent; } - /************************************************************************************************************************************/ - /************************************************************ Geometry3D ************************************************************/ - /************************************************************************************************************************************/ - // - // Predefined figures - // - if ((pNodeElement.Type == CX3DImporter_NodeElement::ENET_Box) || (pNodeElement.Type == CX3DImporter_NodeElement::ENET_Cone) || - (pNodeElement.Type == CX3DImporter_NodeElement::ENET_Cylinder) || (pNodeElement.Type == CX3DImporter_NodeElement::ENET_Sphere)) { - CX3DImporter_NodeElement_Geometry3D &tnemesh = *((CX3DImporter_NodeElement_Geometry3D *)&pNodeElement); // create alias for convenience - std::vector tarr; - - tarr.reserve(tnemesh.Vertices.size()); - for (std::list::iterator it = tnemesh.Vertices.begin(); it != tnemesh.Vertices.end(); ++it) - tarr.push_back(*it); - - *pMesh = StandardShapes::MakeMesh(tarr, static_cast(tnemesh.NumIndices)); // create mesh from vertices using Assimp help. - - return; // mesh is build, nothing to do anymore. - } - // - // Parametric figures - // - if (pNodeElement.Type == CX3DImporter_NodeElement::ENET_ElevationGrid) { - CX3DImporter_NodeElement_ElevationGrid &tnemesh = *((CX3DImporter_NodeElement_ElevationGrid *)&pNodeElement); // create alias for convenience - - // at first create mesh from existing vertices. - *pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIdx, tnemesh.Vertices); - // copy additional information from children - for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { - if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) - MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_Color *)*ch_it)->Value, tnemesh.ColorPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_ColorRGBA) - MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_ColorRGBA *)*ch_it)->Value, tnemesh.ColorPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Normal) - MeshGeometry_AddNormal(**pMesh, ((CX3DImporter_NodeElement_Normal *)*ch_it)->Value, tnemesh.NormalPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_TextureCoordinate) - MeshGeometry_AddTexCoord(**pMesh, ((CX3DImporter_NodeElement_TextureCoordinate *)*ch_it)->Value); - else - throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of ElevationGrid: " + to_string((*ch_it)->Type) + "."); - } // for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) - - return; // mesh is build, nothing to do anymore. - } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_ElevationGrid) - // - // Indexed primitives sets - // - if (pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedFaceSet) { - CX3DImporter_NodeElement_IndexedSet &tnemesh = *((CX3DImporter_NodeElement_IndexedSet *)&pNodeElement); // create alias for convenience - - // at first search for node and create mesh. - for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { - if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { - *pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIndex, ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value); - } - } - - // copy additional information from children - for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { - if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) - MeshGeometry_AddColor(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((CX3DImporter_NodeElement_Color *)*ch_it)->Value, tnemesh.ColorPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_ColorRGBA) - MeshGeometry_AddColor(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((CX3DImporter_NodeElement_ColorRGBA *)*ch_it)->Value, - tnemesh.ColorPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { - } // skip because already read when mesh created. - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Normal) - MeshGeometry_AddNormal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((CX3DImporter_NodeElement_Normal *)*ch_it)->Value, - tnemesh.NormalPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_TextureCoordinate) - MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate *)*ch_it)->Value); - else - throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedFaceSet: " + to_string((*ch_it)->Type) + "."); - } // for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) - - return; // mesh is build, nothing to do anymore. - } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedFaceSet) - - if (pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedLineSet) { - CX3DImporter_NodeElement_IndexedSet &tnemesh = *((CX3DImporter_NodeElement_IndexedSet *)&pNodeElement); // create alias for convenience - - // at first search for node and create mesh. - for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { - if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { - *pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIndex, ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value); - } - } - - // copy additional information from children - for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { - ai_assert(*pMesh); - if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) - MeshGeometry_AddColor(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((CX3DImporter_NodeElement_Color *)*ch_it)->Value, tnemesh.ColorPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_ColorRGBA) - MeshGeometry_AddColor(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((CX3DImporter_NodeElement_ColorRGBA *)*ch_it)->Value, - tnemesh.ColorPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { - } // skip because already read when mesh created. - else - throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedLineSet: " + to_string((*ch_it)->Type) + "."); - } // for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) - - return; // mesh is build, nothing to do anymore. - } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedLineSet) - - if ((pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedTriangleSet) || - (pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedTriangleFanSet) || - (pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedTriangleStripSet)) { - CX3DImporter_NodeElement_IndexedSet &tnemesh = *((CX3DImporter_NodeElement_IndexedSet *)&pNodeElement); // create alias for convenience - - // at first search for node and create mesh. - for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { - if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { - *pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIndex, ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value); - } - } - - // copy additional information from children - for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { - ai_assert(*pMesh); - if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) - MeshGeometry_AddColor(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((CX3DImporter_NodeElement_Color *)*ch_it)->Value, tnemesh.ColorPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_ColorRGBA) - MeshGeometry_AddColor(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((CX3DImporter_NodeElement_ColorRGBA *)*ch_it)->Value, - tnemesh.ColorPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { - } // skip because already read when mesh created. - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Normal) - MeshGeometry_AddNormal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((CX3DImporter_NodeElement_Normal *)*ch_it)->Value, - tnemesh.NormalPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_TextureCoordinate) - MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate *)*ch_it)->Value); - else - throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedTriangleSet or IndexedTriangleFanSet, or \ - IndexedTriangleStripSet: " + - to_string((*ch_it)->Type) + "."); - } // for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) - - return; // mesh is build, nothing to do anymore. - } // if((pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedTriangleFanSet) || (pNodeElement.Type == CX3DImporter_NodeElement::ENET_IndexedTriangleStripSet)) - - if (pNodeElement.Type == CX3DImporter_NodeElement::ENET_Extrusion) { - CX3DImporter_NodeElement_IndexedSet &tnemesh = *((CX3DImporter_NodeElement_IndexedSet *)&pNodeElement); // create alias for convenience - - *pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIndex, tnemesh.Vertices); - - return; // mesh is build, nothing to do anymore. - } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_Extrusion) - - // - // Primitives sets - // - if (pNodeElement.Type == CX3DImporter_NodeElement::ENET_PointSet) { - CX3DImporter_NodeElement_Set &tnemesh = *((CX3DImporter_NodeElement_Set *)&pNodeElement); // create alias for convenience - - // at first search for node and create mesh. - for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { - if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { - std::vector vec_copy; - - vec_copy.reserve(((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value.size()); - for (std::list::const_iterator it = ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value.begin(); - it != ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value.end(); ++it) { - vec_copy.push_back(*it); - } - - *pMesh = StandardShapes::MakeMesh(vec_copy, 1); - } - } - - // copy additional information from children - for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { - ai_assert(*pMesh); - if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) - MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_Color *)*ch_it)->Value, true); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_ColorRGBA) - MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_ColorRGBA *)*ch_it)->Value, true); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { - } // skip because already read when mesh created. - else - throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of PointSet: " + to_string((*ch_it)->Type) + "."); - } // for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) - - return; // mesh is build, nothing to do anymore. - } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_PointSet) - - if (pNodeElement.Type == CX3DImporter_NodeElement::ENET_LineSet) { - CX3DImporter_NodeElement_Set &tnemesh = *((CX3DImporter_NodeElement_Set *)&pNodeElement); // create alias for convenience - - // at first search for node and create mesh. - for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { - if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { - *pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIndex, ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value); - } - } - - // copy additional information from children - for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { - ai_assert(*pMesh); - if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) - MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_Color *)*ch_it)->Value, true); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_ColorRGBA) - MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_ColorRGBA *)*ch_it)->Value, true); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { - } // skip because already read when mesh created. - else - throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of LineSet: " + to_string((*ch_it)->Type) + "."); - } // for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) - - return; // mesh is build, nothing to do anymore. - } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_LineSet) - - if (pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleFanSet) { - CX3DImporter_NodeElement_Set &tnemesh = *((CX3DImporter_NodeElement_Set *)&pNodeElement); // create alias for convenience - - // at first search for node and create mesh. - for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { - if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { - *pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIndex, ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value); - } - } - - // copy additional information from children - for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { - if (nullptr == *pMesh) { - break; - } - if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) - MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_Color *)*ch_it)->Value, tnemesh.ColorPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_ColorRGBA) - MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_ColorRGBA *)*ch_it)->Value, tnemesh.ColorPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { - } // skip because already read when mesh created. - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Normal) - MeshGeometry_AddNormal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((CX3DImporter_NodeElement_Normal *)*ch_it)->Value, - tnemesh.NormalPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_TextureCoordinate) - MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate *)*ch_it)->Value); - else - throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TrianlgeFanSet: " + to_string((*ch_it)->Type) + "."); - } // for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) - - return; // mesh is build, nothing to do anymore. - } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleFanSet) - - if (pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleSet) { - CX3DImporter_NodeElement_Set &tnemesh = *((CX3DImporter_NodeElement_Set *)&pNodeElement); // create alias for convenience - - // at first search for node and create mesh. - for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { - if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { - std::vector vec_copy; - - vec_copy.reserve(((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value.size()); - for (std::list::const_iterator it = ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value.begin(); - it != ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value.end(); ++it) { - vec_copy.push_back(*it); - } - - *pMesh = StandardShapes::MakeMesh(vec_copy, 3); - } - } - - // copy additional information from children - for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { - ai_assert(*pMesh); - if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) - MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_Color *)*ch_it)->Value, tnemesh.ColorPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_ColorRGBA) - MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_ColorRGBA *)*ch_it)->Value, tnemesh.ColorPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { - } // skip because already read when mesh created. - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Normal) - MeshGeometry_AddNormal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((CX3DImporter_NodeElement_Normal *)*ch_it)->Value, - tnemesh.NormalPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_TextureCoordinate) - MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate *)*ch_it)->Value); - else - throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TrianlgeSet: " + to_string((*ch_it)->Type) + "."); - } // for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) - - return; // mesh is build, nothing to do anymore. - } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleSet) - - if (pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleStripSet) { - CX3DImporter_NodeElement_Set &tnemesh = *((CX3DImporter_NodeElement_Set *)&pNodeElement); // create alias for convenience - - // at first search for node and create mesh. - for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { - if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { - *pMesh = GeometryHelper_MakeMesh(tnemesh.CoordIndex, ((CX3DImporter_NodeElement_Coordinate *)*ch_it)->Value); - } - } - - // copy additional information from children - for (std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) { - ai_assert(*pMesh); - if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Color) - MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_Color *)*ch_it)->Value, tnemesh.ColorPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_ColorRGBA) - MeshGeometry_AddColor(**pMesh, ((CX3DImporter_NodeElement_ColorRGBA *)*ch_it)->Value, tnemesh.ColorPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Coordinate) { - } // skip because already read when mesh created. - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_Normal) - MeshGeometry_AddNormal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((CX3DImporter_NodeElement_Normal *)*ch_it)->Value, - tnemesh.NormalPerVertex); - else if ((*ch_it)->Type == CX3DImporter_NodeElement::ENET_TextureCoordinate) - MeshGeometry_AddTexCoord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((CX3DImporter_NodeElement_TextureCoordinate *)*ch_it)->Value); - else - throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TriangleStripSet: " + to_string((*ch_it)->Type) + "."); - } // for(std::list::iterator ch_it = tnemesh.Child.begin(); ch_it != tnemesh.Child.end(); ++ch_it) - - return; // mesh is build, nothing to do anymore. - } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_TriangleStripSet) - - throw DeadlyImportError("Postprocess_BuildMesh. Unknown mesh type: " + to_string(pNodeElement.Type) + "."); -} - -void X3DImporter::Postprocess_BuildNode(const X3DNodeElementBase &pNodeElement, aiNode &pSceneNode, std::list &pSceneMeshList, - std::list &pSceneMaterialList, std::list &pSceneLightList) const { - X3DElementList::const_iterator chit_begin = pNodeElement.Children.begin(); - X3DElementList::const_iterator chit_end = pNodeElement.Children.end(); - std::list SceneNode_Child; - std::list SceneNode_Mesh; - - // At first read all metadata - Postprocess_CollectMetadata(pNodeElement, pSceneNode); - // check if we have deal with grouping node. Which can contain transformation or switch - if (pNodeElement.Type == X3DElemType::ENET_Group) { - const CX3DNodeElementGroup &tne_group = *((CX3DNodeElementGroup*)&pNodeElement); // create alias for convenience - - pSceneNode.mTransformation = tne_group.Transformation; - if (tne_group.UseChoice) { - // If Choice is less than zero or greater than the number of nodes in the children field, nothing is chosen. - if ((tne_group.Choice < 0) || ((size_t)tne_group.Choice >= pNodeElement.Children.size())) { - chit_begin = pNodeElement.Children.end(); - chit_end = pNodeElement.Children.end(); - } else { - for (size_t i = 0; i < (size_t)tne_group.Choice; i++) - ++chit_begin; // forward iterator to chosen node. - - chit_end = chit_begin; - ++chit_end; // point end iterator to next element after chosen node. - } - } // if(tne_group.UseChoice) - } // if(pNodeElement.Type == CX3DImporter_NodeElement::ENET_Group) - - // Reserve memory for fast access and check children. - for (std::list::const_iterator it = chit_begin; it != chit_end; ++it) { // in this loop we do not read metadata because it's already read at begin. - if ((*it)->Type == X3DElemType::ENET_Group) { - // if child is group then create new node and do recursive call. - aiNode *new_node = new aiNode; - - new_node->mName = (*it)->ID; - new_node->mParent = &pSceneNode; - SceneNode_Child.push_back(new_node); - Postprocess_BuildNode(**it, *new_node, pSceneMeshList, pSceneMaterialList, pSceneLightList); - } else if ((*it)->Type == X3DElemType::ENET_Shape) { - // shape can contain only one geometry and one appearance nodes. - Postprocess_BuildShape(*((CX3DImporter_NodeElement_Shape *)*it), SceneNode_Mesh, pSceneMeshList, pSceneMaterialList); - } else if (((*it)->Type == X3DElemType::ENET_DirectionalLight) || ((*it)->Type == X3DElemType::ENET_PointLight) || - ((*it)->Type == X3DElemType::ENET_SpotLight)) { - Postprocess_BuildLight(*((X3DElemType *)*it), pSceneLightList); - } else if (!PostprocessHelper_ElementIsMetadata((*it)->Type)) // skip metadata - { - throw DeadlyImportError("Postprocess_BuildNode. Unknown type: " + to_string((*it)->Type) + "."); - } - } // for(std::list::const_iterator it = chit_begin; it != chit_end; it++) - - // copy data about children and meshes to aiNode. - if (!SceneNode_Child.empty()) { - std::list::const_iterator it = SceneNode_Child.begin(); - - pSceneNode.mNumChildren = static_cast(SceneNode_Child.size()); - pSceneNode.mChildren = new aiNode *[pSceneNode.mNumChildren]; - for (size_t i = 0; i < pSceneNode.mNumChildren; i++) - pSceneNode.mChildren[i] = *it++; - } - - if (!SceneNode_Mesh.empty()) { - std::list::const_iterator it = SceneNode_Mesh.begin(); - - pSceneNode.mNumMeshes = static_cast(SceneNode_Mesh.size()); - pSceneNode.mMeshes = new unsigned int[pSceneNode.mNumMeshes]; - for (size_t i = 0; i < pSceneNode.mNumMeshes; i++) - pSceneNode.mMeshes[i] = *it++; - } - - // that's all. return to previous deals -} - -void X3DImporter::Postprocess_BuildShape(const CX3DImporter_NodeElement_Shape &pShapeNodeElement, std::list &pNodeMeshInd, - std::list &pSceneMeshList, std::list &pSceneMaterialList) const { - aiMaterial *tmat = nullptr; - aiMesh *tmesh = nullptr; - X3DElemType mesh_type = X3DElemType::ENET_Invalid; - unsigned int mat_ind = 0; - - for (X3DElementList::const_iterator it = pShapeNodeElement.Children.begin(); it != pShapeNodeElement.Children.end(); ++it) { - if (PostprocessHelper_ElementIsMesh((*it)->Type)) { - Postprocess_BuildMesh(**it, &tmesh); - if (tmesh != nullptr) { - // if mesh successfully built then add data about it to arrays - pNodeMeshInd.push_back(static_cast(pSceneMeshList.size())); - pSceneMeshList.push_back(tmesh); - // keep mesh type. Need above for texture coordinate generation. - mesh_type = (*it)->Type; - } - } else if ((*it)->Type == X3DElemType::ENET_Appearance) { - Postprocess_BuildMaterial(**it, &tmat); - if (tmat != nullptr) { - // if material successfully built then add data about it to array - mat_ind = static_cast(pSceneMaterialList.size()); - pSceneMaterialList.push_back(tmat); - } - } - } // for(std::list::const_iterator it = pShapeNodeElement.Child.begin(); it != pShapeNodeElement.Child.end(); it++) - - // associate read material with read mesh. - if ((tmesh != nullptr) && (tmat != nullptr)) { - tmesh->mMaterialIndex = mat_ind; - // Check texture mapping. If material has texture but mesh has no texture coordinate then try to ask Assimp to generate texture coordinates. - if ((tmat->GetTextureCount(aiTextureType_DIFFUSE) != 0) && !tmesh->HasTextureCoords(0)) { - int32_t tm; - aiVector3D tvec3; - - switch (mesh_type) { - case X3DElemType::ENET_Box: - tm = aiTextureMapping_BOX; - break; - case X3DElemType::ENET_Cone: - case X3DElemType::ENET_Cylinder: - tm = aiTextureMapping_CYLINDER; - break; - case X3DElemType::ENET_Sphere: - tm = aiTextureMapping_SPHERE; - break; - default: - tm = aiTextureMapping_PLANE; - break; - } // switch(mesh_type) - - tmat->AddProperty(&tm, 1, AI_MATKEY_MAPPING_DIFFUSE(0)); - } // if((tmat->GetTextureCount(aiTextureType_DIFFUSE) != 0) && !tmesh->HasTextureCoords(0)) - } // if((tmesh != nullptr) && (tmat != nullptr)) -} - -void X3DImporter::Postprocess_CollectMetadata(const CX3DImporter_NodeElement &pNodeElement, aiNode &pSceneNode) const { - X3DElementList meta_list; - size_t meta_idx; - - PostprocessHelper_CollectMetadata(pNodeElement, meta_list); // find metadata in current node element. - if (!meta_list.empty()) { - if (pSceneNode.mMetaData != nullptr) { - throw DeadlyImportError("Postprocess. MetaData member in node are not nullptr. Something went wrong."); - } - - // copy collected metadata to output node. - pSceneNode.mMetaData = aiMetadata::Alloc(static_cast(meta_list.size())); - meta_idx = 0; - for (X3DElementList::const_iterator it = meta_list.begin(); it != meta_list.end(); ++it, ++meta_idx) { - CX3DImporter_NodeElement_Meta *cur_meta = (CX3DImporter_NodeElement_Meta *)*it; - - // due to limitations we can add only first element of value list. - // Add an element according to its type. - if ((*it)->Type == CX3DImporter_NodeElement::ENET_MetaBoolean) { - if (((CX3DImporter_NodeElement_MetaBoolean *)cur_meta)->Value.size() > 0) - pSceneNode.mMetaData->Set(static_cast(meta_idx), cur_meta->Name, *(((CX3DImporter_NodeElement_MetaBoolean *)cur_meta)->Value.begin())); - } else if ((*it)->Type == CX3DImporter_NodeElement::ENET_MetaDouble) { - if (((CX3DImporter_NodeElement_MetaDouble *)cur_meta)->Value.size() > 0) - pSceneNode.mMetaData->Set(static_cast(meta_idx), cur_meta->Name, (float)*(((CX3DImporter_NodeElement_MetaDouble *)cur_meta)->Value.begin())); - } else if ((*it)->Type == CX3DImporter_NodeElement::ENET_MetaFloat) { - if (((CX3DImporter_NodeElement_MetaFloat *)cur_meta)->Value.size() > 0) - pSceneNode.mMetaData->Set(static_cast(meta_idx), cur_meta->Name, *(((CX3DImporter_NodeElement_MetaFloat *)cur_meta)->Value.begin())); - } else if ((*it)->Type == CX3DImporter_NodeElement::ENET_MetaInteger) { - if (((CX3DImporter_NodeElement_MetaInteger *)cur_meta)->Value.size() > 0) - pSceneNode.mMetaData->Set(static_cast(meta_idx), cur_meta->Name, *(((CX3DImporter_NodeElement_MetaInteger *)cur_meta)->Value.begin())); - } else if ((*it)->Type == CX3DImporter_NodeElement::ENET_MetaString) { - if (((CX3DImporter_NodeElement_MetaString *)cur_meta)->Value.size() > 0) { - aiString tstr(((CX3DImporter_NodeElement_MetaString *)cur_meta)->Value.begin()->data()); - - pSceneNode.mMetaData->Set(static_cast(meta_idx), cur_meta->Name, tstr); - } - } else { - throw DeadlyImportError("Postprocess. Unknown metadata type."); - } // if((*it)->Type == CX3DImporter_NodeElement::ENET_Meta*) else - } // for(std::list::const_iterator it = meta_list.begin(); it != meta_list.end(); it++) - } // if( !meta_list.empty() ) } #endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/code/AssetLib/X3D/X3DImporter.hpp b/code/AssetLib/X3D/X3DImporter.hpp index 559e4932f..c9509a035 100644 --- a/code/AssetLib/X3D/X3DImporter.hpp +++ b/code/AssetLib/X3D/X3DImporter.hpp @@ -41,6 +41,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef INCLUDED_AI_X3D_IMPORTER_H #define INCLUDED_AI_X3D_IMPORTER_H +#include "X3DImporter_Node.hpp" + #include #include #include @@ -51,7 +53,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include namespace Assimp { @@ -68,6 +69,21 @@ inline void Throw_ConvertFail_Str2ArrF(const std::string &nodeName, const std::s "\" from string to array of floats."); } +inline void Throw_ConvertFail_Str2ArrD(const std::string &nodeName, const std::string &pAttrValue) { + throw DeadlyImportError("In <" + nodeName + "> failed to convert attribute value \"" + pAttrValue + + "\" from string to array of doubles."); +} + +inline void Throw_ConvertFail_Str2ArrB(const std::string &nodeName, const std::string &pAttrValue) { + throw DeadlyImportError("In <" + nodeName + "> failed to convert attribute value \"" + pAttrValue + + "\" from string to array of booleans."); +} + +inline void Throw_ConvertFail_Str2ArrI(const std::string &nodeName, const std::string &pAttrValue) { + throw DeadlyImportError("In <" + nodeName + "> failed to convert attribute value \"" + pAttrValue + + "\" from string to array of integers."); +} + inline void Throw_DEF_And_USE(const std::string &nodeName) { throw DeadlyImportError("\"DEF\" and \"USE\" can not be defined both in <" + nodeName + ">."); } @@ -225,410 +241,8 @@ inline void LogInfo(const std::string &message) { /// /// That's all for now. Enjoy /// -enum class X3DElemType { - ENET_Group, ///< Element has type "Group". - ENET_MetaBoolean, ///< Element has type "Metadata boolean". - ENET_MetaDouble, ///< Element has type "Metadata double". - ENET_MetaFloat, ///< Element has type "Metadata float". - ENET_MetaInteger, ///< Element has type "Metadata integer". - ENET_MetaSet, ///< Element has type "Metadata set". - ENET_MetaString, ///< Element has type "Metadata string". - ENET_Arc2D, ///< Element has type "Arc2D". - ENET_ArcClose2D, ///< Element has type "ArcClose2D". - ENET_Circle2D, ///< Element has type "Circle2D". - ENET_Disk2D, ///< Element has type "Disk2D". - ENET_Polyline2D, ///< Element has type "Polyline2D". - ENET_Polypoint2D, ///< Element has type "Polypoint2D". - ENET_Rectangle2D, ///< Element has type "Rectangle2D". - ENET_TriangleSet2D, ///< Element has type "TriangleSet2D". - ENET_Box, ///< Element has type "Box". - ENET_Cone, ///< Element has type "Cone". - ENET_Cylinder, ///< Element has type "Cylinder". - ENET_Sphere, ///< Element has type "Sphere". - ENET_ElevationGrid, ///< Element has type "ElevationGrid". - ENET_Extrusion, ///< Element has type "Extrusion". - ENET_Coordinate, ///< Element has type "Coordinate". - ENET_Normal, ///< Element has type "Normal". - ENET_TextureCoordinate, ///< Element has type "TextureCoordinate". - ENET_IndexedFaceSet, ///< Element has type "IndexedFaceSet". - ENET_IndexedLineSet, ///< Element has type "IndexedLineSet". - ENET_IndexedTriangleSet, ///< Element has type "IndexedTriangleSet". - ENET_IndexedTriangleFanSet, ///< Element has type "IndexedTriangleFanSet". - ENET_IndexedTriangleStripSet, ///< Element has type "IndexedTriangleStripSet". - ENET_LineSet, ///< Element has type "LineSet". - ENET_PointSet, ///< Element has type "PointSet". - ENET_TriangleSet, ///< Element has type "TriangleSet". - ENET_TriangleFanSet, ///< Element has type "TriangleFanSet". - ENET_TriangleStripSet, ///< Element has type "TriangleStripSet". - ENET_Color, ///< Element has type "Color". - ENET_ColorRGBA, ///< Element has type "ColorRGBA". - ENET_Shape, ///< Element has type "Shape". - ENET_Appearance, ///< Element has type "Appearance". - ENET_Material, ///< Element has type "Material". - ENET_ImageTexture, ///< Element has type "ImageTexture". - ENET_TextureTransform, ///< Element has type "TextureTransform". - ENET_DirectionalLight, ///< Element has type "DirectionalLight". - ENET_PointLight, ///< Element has type "PointLight". - ENET_SpotLight, ///< Element has type "SpotLight". - ENET_Invalid ///< Element has invalid type and possible contain invalid data. -}; - -struct X3DNodeElementBase { - X3DNodeElementBase *Parent; - std::string ID; - std::list Children; - X3DElemType Type; - -protected: - X3DNodeElementBase(X3DElemType type, X3DNodeElementBase *pParent) : - Type(type), Parent(pParent) { - // empty - } -}; - -/// This struct hold value. -struct CX3DImporter_NodeElement_Color : X3DNodeElementBase { - std::list Value; ///< Stored value. - - /// Constructor - /// \param [in] pParent - pointer to parent node. - CX3DImporter_NodeElement_Color(X3DNodeElementBase *pParent) : - X3DNodeElementBase(X3DElemType::ENET_Color, pParent) {} - -}; // struct CX3DImporter_NodeElement_Color - -/// This struct hold value. -struct CX3DImporter_NodeElement_ColorRGBA : X3DNodeElementBase { - std::list Value; ///< Stored value. - - /// Constructor - /// \param [in] pParent - pointer to parent node. - CX3DImporter_NodeElement_ColorRGBA(X3DNodeElementBase *pParent) : - X3DNodeElementBase(X3DElemType::ENET_ColorRGBA, pParent) {} - -}; // struct CX3DImporter_NodeElement_ColorRGBA - -/// This struct hold value. -struct CX3DImporter_NodeElement_Coordinate : public X3DNodeElementBase { - std::list Value; ///< Stored value. - - /// Constructor - /// \param [in] pParent - pointer to parent node. - CX3DImporter_NodeElement_Coordinate(X3DNodeElementBase *pParent) : - X3DNodeElementBase(X3DElemType::ENET_Coordinate, pParent) {} - -}; // struct CX3DImporter_NodeElement_Coordinate - -/// This struct hold value. -struct CX3DImporter_NodeElement_Normal : X3DNodeElementBase { - std::list Value; ///< Stored value. - - /// Constructor - /// \param [in] pParent - pointer to parent node. - CX3DImporter_NodeElement_Normal(X3DNodeElementBase *pParent) : - X3DNodeElementBase(X3DElemType::ENET_Normal, pParent) {} - -}; // struct CX3DImporter_NodeElement_Normal - -/// This struct hold value. -struct CX3DImporter_NodeElement_TextureCoordinate : X3DNodeElementBase { - std::list Value; ///< Stored value. - - /// Constructor - /// \param [in] pParent - pointer to parent node. - CX3DImporter_NodeElement_TextureCoordinate(X3DNodeElementBase *pParent) : - X3DNodeElementBase(X3DElemType::ENET_TextureCoordinate, pParent) {} - -}; // struct CX3DImporter_NodeElement_TextureCoordinate - -/// Two-dimensional figure. -struct CX3DImporter_NodeElement_Geometry2D : X3DNodeElementBase { - std::list Vertices; ///< Vertices list. - size_t NumIndices; ///< Number of indices in one face. - bool Solid; ///< Flag: if true then render must use back-face culling, else render must draw both sides of object. - - /// Constructor. - /// \param [in] pParent - pointer to parent node. - /// \param [in] pType - type of geometry object. - CX3DImporter_NodeElement_Geometry2D(X3DElemType pType, X3DNodeElementBase *pParent) : - X3DNodeElementBase(pType, pParent), Solid(true) {} - -}; // class CX3DImporter_NodeElement_Geometry2D - -/// Three-dimensional body. -struct CX3DImporter_NodeElement_Geometry3D : X3DNodeElementBase { - std::list Vertices; ///< Vertices list. - size_t NumIndices; ///< Number of indices in one face. - bool Solid; ///< Flag: if true then render must use back-face culling, else render must draw both sides of object. - - /// Constructor. - /// \param [in] pParent - pointer to parent node. - /// \param [in] pType - type of geometry object. - CX3DImporter_NodeElement_Geometry3D(X3DElemType pType, X3DNodeElementBase *pParent) : - X3DNodeElementBase(pType, pParent), Vertices(), NumIndices(0), Solid(true) { - // empty - } -}; // class CX3DImporter_NodeElement_Geometry3D - -/// Uniform rectangular grid of varying height. -struct CX3DImporter_NodeElement_ElevationGrid : CX3DImporter_NodeElement_Geometry3D { - bool NormalPerVertex; ///< If true then normals are defined for every vertex, else for every face(line). - bool ColorPerVertex; ///< If true then colors are defined for every vertex, else for every face(line). - /// If the angle between the geometric normals of two adjacent faces is less than the crease angle, normals shall be calculated so that the faces are - /// shaded smoothly across the edge; otherwise, normals shall be calculated so that a lighting discontinuity across the edge is produced. - float CreaseAngle; - std::vector CoordIdx; ///< Coordinates list by faces. In X3D format: "-1" - delimiter for faces. - - /// Constructor. - /// \param [in] pParent - pointer to parent node. - /// \param [in] pType - type of geometry object. - CX3DImporter_NodeElement_ElevationGrid(X3DElemType pType, X3DNodeElementBase *pParent) : - CX3DImporter_NodeElement_Geometry3D(pType, pParent) {} -}; // class CX3DImporter_NodeElement_IndexedSet - -/// Shape with indexed vertices. -struct CX3DImporter_NodeElement_IndexedSet : public CX3DImporter_NodeElement_Geometry3D { - /// The ccw field defines the ordering of the vertex coordinates of the geometry with respect to user-given or automatically generated normal vectors - /// used in the lighting model equations. If ccw is TRUE, the normals shall follow the right hand rule; the orientation of each normal with respect to - /// the vertices (taken in order) shall be such that the vertices appear to be oriented in a counterclockwise order when the vertices are viewed (in the - /// local coordinate system of the Shape) from the opposite direction as the normal. If ccw is FALSE, the normals shall be oriented in the opposite - /// direction. If normals are not generated but are supplied using a Normal node, and the orientation of the normals does not match the setting of the - /// ccw field, results are undefined. - bool CCW; - std::vector ColorIndex; ///< Field to specify the polygonal faces by indexing into the or . - bool ColorPerVertex; ///< If true then colors are defined for every vertex, else for every face(line). - /// The convex field indicates whether all polygons in the shape are convex (TRUE). A polygon is convex if it is planar, does not intersect itself, - /// and all of the interior angles at its vertices are less than 180 degrees. Non planar and self intersecting polygons may produce undefined results - /// even if the convex field is FALSE. - bool Convex; - std::vector CoordIndex; ///< Field to specify the polygonal faces by indexing into the . - /// If the angle between the geometric normals of two adjacent faces is less than the crease angle, normals shall be calculated so that the faces are - /// shaded smoothly across the edge; otherwise, normals shall be calculated so that a lighting discontinuity across the edge is produced. - float CreaseAngle; - std::vector NormalIndex; ///< Field to specify the polygonal faces by indexing into the . - bool NormalPerVertex; ///< If true then normals are defined for every vertex, else for every face(line). - std::vector TexCoordIndex; ///< Field to specify the polygonal faces by indexing into the . - - /// Constructor. - /// \param [in] pParent - pointer to parent node. - /// \param [in] pType - type of geometry object. - CX3DImporter_NodeElement_IndexedSet(X3DElemType pType, X3DNodeElementBase *pParent) : - CX3DImporter_NodeElement_Geometry3D(pType, pParent) {} -}; // class CX3DImporter_NodeElement_IndexedSet - -/// Shape with set of vertices. -struct CX3DImporter_NodeElement_Set : CX3DImporter_NodeElement_Geometry3D { - /// The ccw field defines the ordering of the vertex coordinates of the geometry with respect to user-given or automatically generated normal vectors - /// used in the lighting model equations. If ccw is TRUE, the normals shall follow the right hand rule; the orientation of each normal with respect to - /// the vertices (taken in order) shall be such that the vertices appear to be oriented in a counterclockwise order when the vertices are viewed (in the - /// local coordinate system of the Shape) from the opposite direction as the normal. If ccw is FALSE, the normals shall be oriented in the opposite - /// direction. If normals are not generated but are supplied using a Normal node, and the orientation of the normals does not match the setting of the - /// ccw field, results are undefined. - bool CCW; - bool ColorPerVertex; ///< If true then colors are defined for every vertex, else for every face(line). - bool NormalPerVertex; ///< If true then normals are defined for every vertex, else for every face(line). - std::vector CoordIndex; ///< Field to specify the polygonal faces by indexing into the . - std::vector NormalIndex; ///< Field to specify the polygonal faces by indexing into the . - std::vector TexCoordIndex; ///< Field to specify the polygonal faces by indexing into the . - std::vector VertexCount; ///< Field describes how many vertices are to be used in each polyline(polygon) from the field. - - /// Constructor. - /// \param [in] pParent - pointer to parent node. - /// \param [in] pType - type of geometry object. - CX3DImporter_NodeElement_Set(X3DElemType type, X3DNodeElementBase *pParent) : - CX3DImporter_NodeElement_Geometry3D(type, pParent) {} - -}; // class CX3DImporter_NodeElement_Set - -/// This struct hold value. -struct CX3DImporter_NodeElement_Shape : X3DNodeElementBase { - - /// Constructor - /// \param [in] pParent - pointer to parent node. - CX3DImporter_NodeElement_Shape(X3DNodeElementBase *pParent) : - X3DNodeElementBase(X3DElemType::ENET_Shape, pParent) {} -}; // struct CX3DImporter_NodeElement_Shape - -/// This struct hold value. -struct CX3DImporter_NodeElement_Appearance : public X3DNodeElementBase { - - /// Constructor - /// \param [in] pParent - pointer to parent node. - CX3DImporter_NodeElement_Appearance(X3DNodeElementBase *pParent) : - X3DNodeElementBase(X3DElemType::ENET_Appearance, pParent) {} - -}; // struct CX3DImporter_NodeElement_Appearance - -struct CX3DImporter_NodeElement_Material : public X3DNodeElementBase { - float AmbientIntensity; ///< Specifies how much ambient light from light sources this surface shall reflect. - aiColor3D DiffuseColor; ///< Reflects all X3D light sources depending on the angle of the surface with respect to the light source. - aiColor3D EmissiveColor; ///< Models "glowing" objects. This can be useful for displaying pre-lit models. - float Shininess; ///< Lower shininess values produce soft glows, while higher values result in sharper, smaller highlights. - aiColor3D SpecularColor; ///< The specularColor and shininess fields determine the specular highlights. - float Transparency; ///< Specifies how "clear" an object is, with 1.0 being completely transparent, and 0.0 completely opaque. - - /// Constructor. - /// \param [in] pParent - pointer to parent node. - /// \param [in] pType - type of geometry object. - CX3DImporter_NodeElement_Material(X3DNodeElementBase *pParent) : - X3DNodeElementBase(X3DElemType::ENET_Material, pParent), - AmbientIntensity(0.0f), - DiffuseColor(), - EmissiveColor(), - Shininess(0.0f), - SpecularColor(), - Transparency(1.0f) { - // empty - } -}; // class CX3DImporter_NodeElement_Material - -/// This struct hold value. -struct CX3DImporter_NodeElement_ImageTexture : X3DNodeElementBase { - /// RepeatS and RepeatT, that specify how the texture wraps in the S and T directions. If repeatS is TRUE (the default), the texture map is repeated - /// outside the [0.0, 1.0] texture coordinate range in the S direction so that it fills the shape. If repeatS is FALSE, the texture coordinates are - /// clamped in the S direction to lie within the [0.0, 1.0] range. The repeatT field is analogous to the repeatS field. - bool RepeatS; - bool RepeatT; ///< See \ref RepeatS. - std::string URL; ///< URL of the texture. - - /// Constructor - /// \param [in] pParent - pointer to parent node. - CX3DImporter_NodeElement_ImageTexture(X3DNodeElementBase *pParent) : - X3DNodeElementBase(X3DElemType::ENET_ImageTexture, pParent) {} - -}; // struct CX3DImporter_NodeElement_ImageTexture - -/// This struct hold value. -struct CX3DImporter_NodeElement_TextureTransform : X3DNodeElementBase { - aiVector2D Center; ///< Specifies a translation offset in texture coordinate space about which the rotation and scale fields are applied. - float Rotation; ///< Specifies a rotation in angle base units of the texture coordinates about the center point after the scale has been applied. - aiVector2D Scale; ///< Specifies a scaling factor in S and T of the texture coordinates about the center point. - aiVector2D Translation; ///< Specifies a translation of the texture coordinates. - - /// Constructor - /// \param [in] pParent - pointer to parent node. - CX3DImporter_NodeElement_TextureTransform(X3DNodeElementBase *pParent) : - X3DNodeElementBase(X3DElemType::ENET_TextureTransform, pParent) {} - -}; // struct CX3DImporter_NodeElement_TextureTransform - -struct CX3DNodeElementGroup : X3DNodeElementBase { - aiMatrix4x4 Transformation; ///< Transformation matrix. - - /// As you know node elements can use already defined node elements when attribute "USE" is defined. - /// Standard search when looking for an element in the whole scene graph, existing at this moment. - /// If a node is marked as static, the children(or lower) can not search for elements in the nodes upper then static. - bool Static; - - bool UseChoice; ///< Flag: if true then use number from \ref Choice to choose what the child will be kept. - int32_t Choice; ///< Number of the child which will be kept. - - /// Constructor. - /// \param [in] pParent - pointer to parent node. - /// \param [in] pStatic - static node flag. - CX3DNodeElementGroup(X3DNodeElementBase *pParent, const bool pStatic = false) : - X3DNodeElementBase(X3DElemType::ENET_Group, pParent), Static(pStatic), UseChoice(false) {} -}; - -struct X3DNodeElementMeta : X3DNodeElementBase { - std::string Name; ///< Name of metadata object. - std::string Reference; - - virtual ~X3DNodeElementMeta() { - // empty - } - -protected: - X3DNodeElementMeta(X3DElemType type, X3DNodeElementBase *parent) : - X3DNodeElementBase(type, parent) { - // empty - } -}; - -struct X3DNodeElementMetaBoolean : X3DNodeElementMeta { - std::vector Value; ///< Stored value. - - explicit X3DNodeElementMetaBoolean(X3DNodeElementBase *pParent) : - X3DNodeElementMeta(X3DElemType::ENET_MetaBoolean, pParent) { - // empty - } -}; - -struct X3DNodeElementMetaDouble : X3DNodeElementMeta { - std::vector Value; ///< Stored value. - - explicit X3DNodeElementMetaDouble(X3DNodeElementBase *pParent) : - X3DNodeElementMeta(X3DElemType::ENET_MetaDouble, pParent) { - // empty - } -}; - -struct X3DNodeElementMetaFloat : public X3DNodeElementMeta { - std::vector Value; ///< Stored value. - - explicit X3DNodeElementMetaFloat(X3DNodeElementBase *pParent) : - X3DNodeElementMeta(X3DElemType::ENET_MetaFloat, pParent) { - // empty - } -}; - -struct X3DNodeElementMetaInt : public X3DNodeElementMeta { - std::vector Value; ///< Stored value. - - explicit X3DNodeElementMetaInt(X3DNodeElementBase *pParent) : - X3DNodeElementMeta(X3DElemType::ENET_MetaInteger, pParent) { - // empty - } -}; - -struct X3DNodeElementMetaSet : public X3DNodeElementMeta { - std::list Value; ///< Stored value. - - explicit X3DNodeElementMetaSet(X3DNodeElementBase *pParent) : - X3DNodeElementMeta(X3DElemType::ENET_MetaSet, pParent) { - // empty - } -}; - -struct X3DNodeElementMetaString : X3DNodeElementMeta { - std::list Value; ///< Stored value. - - explicit X3DNodeElementMetaString(X3DNodeElementBase *pParent) : - X3DNodeElementMeta(X3DElemType::ENET_MetaString, pParent) { - // empty - } -}; - -/// \struct CX3DImporter_NodeElement_Light -/// This struct hold value. -struct X3DNodeNodeElementLight : X3DNodeElementBase { - float AmbientIntensity; ///< Specifies the intensity of the ambient emission from the light. - aiColor3D Color; ///< specifies the spectral colour properties of both the direct and ambient light emission as an RGB value. - aiVector3D Direction; ///< Specifies the direction vector of the illumination emanating from the light source in the local coordinate system. - /// \var Global - /// Field that determines whether the light is global or scoped. Global lights illuminate all objects that fall within their volume of lighting influence. - /// Scoped lights only illuminate objects that are in the same transformation hierarchy as the light. - bool Global; - float Intensity; ///< Specifies the brightness of the direct emission from the light. - /// \var Attenuation - /// PointLight node's illumination falls off with distance as specified by three attenuation coefficients. The attenuation factor - /// is: "1 / max(attenuation[0] + attenuation[1] * r + attenuation[2] * r2, 1)", where r is the distance from the light to the surface being illuminated. - aiVector3D Attenuation; - aiVector3D Location; ///< Specifies a translation offset of the centre point of the light source from the light's local coordinate system origin. - float Radius; ///< Specifies the radial extent of the solid angle and the maximum distance from location that may be illuminated by the light source. - float BeamWidth; ///< Specifies an inner solid angle in which the light source emits light at uniform full intensity. - float CutOffAngle; ///< The light source's emission intensity drops off from the inner solid angle (beamWidth) to the outer solid angle (cutOffAngle). - - /// Constructor - /// \param [in] pParent - pointer to parent node. - /// \param [in] pLightType - type of the light source. - X3DNodeNodeElementLight(X3DElemType pLightType, X3DNodeElementBase *pParent) : - X3DNodeElementBase(pLightType, pParent) {} - -}; // struct CX3DImporter_NodeElement_Light - -using X3DElementList = std::list; +using X3DElementList = std::list; class X3DImporter : public BaseImporter { public: @@ -654,18 +268,113 @@ public: void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler); const aiImporterDesc *GetInfo() const; void Clear(); - void readMetadata(XmlNode &node); - void readScene(XmlNode &node); - void readViewpoint(XmlNode &node); - void readMetadataObject(XmlNode &node); - void ParseDirectionalLight(XmlNode &node); - void Postprocess_BuildNode(const X3DNodeElementBase &pNodeElement, aiNode &pSceneNode, std::list &pSceneMeshList, - std::list &pSceneMaterialList, std::list &pSceneLightList) const; private: + bool isNodeEmpty(XmlNode &node); + void checkNodeMustBeEmpty(XmlNode &node); + void skipUnsupportedNode(const std::string &pParentNodeName, XmlNode &node); + void readHead(XmlNode &node); + void readChildNodes(XmlNode &node, const std::string &pParentNodeName); + void readScene(XmlNode &node); + + bool FindNodeElement_FromRoot(const std::string &pID, const X3DElemType pType, X3DNodeElementBase **pElement); + bool FindNodeElement_FromNode(X3DNodeElementBase *pStartNode, const std::string &pID, + const X3DElemType pType, X3DNodeElementBase **pElement); + bool FindNodeElement(const std::string &pID, const X3DElemType pType, X3DNodeElementBase **pElement); + void ParseHelper_Group_Begin(const bool pStatic = false); + void ParseHelper_Node_Enter(X3DNodeElementBase *pNode); + void ParseHelper_Node_Exit(); + + // 2D geometry + void readArc2D(XmlNode &node); + void readArcClose2D(XmlNode &node); + void readCircle2D(XmlNode &node); + void readDisk2D(XmlNode &node); + void readPolyline2D(XmlNode &node); + void readPolypoint2D(XmlNode &node); + void readRectangle2D(XmlNode &node); + void readTriangleSet2D(XmlNode &node); + + // 3D geometry + void readBox(XmlNode &node); + void readCone(XmlNode &node); + void readCylinder(XmlNode &node); + void readElevationGrid(XmlNode &node); + void readExtrusion(XmlNode &node); + void readIndexedFaceSet(XmlNode &node); + void readSphere(XmlNode &node); + + // group + void startReadGroup(XmlNode &node); + void endReadGroup(); + void startReadStaticGroup(XmlNode &node); + void endReadStaticGroup(); + void startReadSwitch(XmlNode &node); + void endReadSwitch(); + void startReadTransform(XmlNode &node); + void endReadTransform(); + + // light + void readDirectionalLight(XmlNode &node); + void readPointLight(XmlNode &node); + void readSpotLight(XmlNode &node); + + // metadata + bool checkForMetadataNode(XmlNode &node); + void childrenReadMetadata(XmlNode &node, X3DNodeElementBase *pParentElement, const std::string &pNodeName); + void readMetadataBoolean(XmlNode &node); + void readMetadataDouble(XmlNode &node); + void readMetadataFloat(XmlNode &node); + void readMetadataInteger(XmlNode &node); + void readMetadataSet(XmlNode &node); + void readMetadataString(XmlNode &node); + + // networking + void readInline(XmlNode &node); + + // postprocessing + aiMatrix4x4 PostprocessHelper_Matrix_GlobalToCurrent() const; + void PostprocessHelper_CollectMetadata(const X3DNodeElementBase &pNodeElement, std::list &pList) const; + bool PostprocessHelper_ElementIsMetadata(const X3DElemType pType) const; + bool PostprocessHelper_ElementIsMesh(const X3DElemType pType) const; + void Postprocess_BuildLight(const X3DNodeElementBase &pNodeElement, std::list &pSceneLightList) const; + void Postprocess_BuildMaterial(const X3DNodeElementBase &pNodeElement, aiMaterial **pMaterial) const; + void Postprocess_BuildMesh(const X3DNodeElementBase &pNodeElement, aiMesh **pMesh) const; + void Postprocess_BuildNode(const X3DNodeElementBase &pNodeElement, aiNode &pSceneNode, std::list &pSceneMeshList, + std::list &pSceneMaterialList, std::list &pSceneLightList) const; + void Postprocess_BuildShape(const X3DNodeElementShape &pShapeNodeElement, std::list &pNodeMeshInd, + std::list &pSceneMeshList, std::list &pSceneMaterialList) const; + void Postprocess_CollectMetadata(const X3DNodeElementBase &pNodeElement, aiNode &pSceneNode) const; + + // rendering + void readColor(XmlNode &node); + void readColorRGBA(XmlNode &node); + void readCoordinate(XmlNode &node); + void readIndexedLineSet(XmlNode &node); + void readIndexedTriangleFanSet(XmlNode &node); + void readIndexedTriangleSet(XmlNode &node); + void readIndexedTriangleStripSet(XmlNode &node); + void readLineSet(XmlNode &node); + void readPointSet(XmlNode &node); + void readTriangleFanSet(XmlNode &node); + void readTriangleSet(XmlNode &node); + void readTriangleStripSet(XmlNode &node); + void readNormal(XmlNode &node); + + // shape + void readShape(XmlNode &node); + void readAppearance(XmlNode &node); + void readMaterial(XmlNode &node); + + // texturing + void readImageTexture(XmlNode &node); + void readTextureCoordinate(XmlNode &node); + void readTextureTransform(XmlNode &node); + static const aiImporterDesc Description; X3DNodeElementBase *mNodeElementCur; aiScene *mScene; + IOSystem *mpIOHandler; }; // class X3DImporter } // namespace Assimp diff --git a/code/AssetLib/X3D/X3DImporter_Geometry2D.cpp b/code/AssetLib/X3D/X3DImporter_Geometry2D.cpp new file mode 100644 index 000000000..171075556 --- /dev/null +++ b/code/AssetLib/X3D/X3DImporter_Geometry2D.cpp @@ -0,0 +1,467 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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. + +---------------------------------------------------------------------- +*/ +/// \file X3DImporter_Geometry2D.cpp +/// \brief Parsing data from nodes of "Geometry2D" set of X3D. +/// date 2015-2016 +/// author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" +#include "X3DXmlHelper.h" +#include "X3DGeoHelper.h" + +namespace Assimp { + +// +// The Arc2D node specifies a linear circular arc whose center is at (0,0) and whose angles are measured starting at the positive x-axis and sweeping +// towards the positive y-axis. The radius field specifies the radius of the circle of which the arc is a portion. The arc extends from the startAngle +// counterclockwise to the endAngle. The values of startAngle and endAngle shall be in the range [-2pi, 2pi] radians (or the equivalent if a different +// angle base unit has been specified). If startAngle and endAngle have the same value, a circle is specified. +void X3DImporter::readArc2D(XmlNode &node) { + std::string def, use; + float endAngle = AI_MATH_HALF_PI_F; + float radius = 1; + float startAngle = 0; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getFloatAttribute(node, "endAngle", endAngle); + XmlParser::getFloatAttribute(node, "radius", radius); + XmlParser::getFloatAttribute(node, "startAngle", startAngle); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Arc2D, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_Arc2D, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // create point list of geometry object and convert it to line set. + std::list tlist; + + X3DGeoHelper::make_arc2D(startAngle, endAngle, radius, 10, tlist); ///TODO: IME - AI_CONFIG for NumSeg + X3DGeoHelper::extend_point_to_line(tlist, ((X3DNodeElementGeometry2D *)ne)->Vertices); + ((X3DNodeElementGeometry2D *)ne)->NumIndices = 2; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Arc2D"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +// The ArcClose node specifies a portion of a circle whose center is at (0,0) and whose angles are measured starting at the positive x-axis and sweeping +// towards the positive y-axis. The end points of the arc specified are connected as defined by the closureType field. The radius field specifies the radius +// of the circle of which the arc is a portion. The arc extends from the startAngle counterclockwise to the endAngle. The value of radius shall be greater +// than zero. The values of startAngle and endAngle shall be in the range [-2pi, 2pi] radians (or the equivalent if a different default angle base unit has +// been specified). If startAngle and endAngle have the same value, a circle is specified and closureType is ignored. If the absolute difference between +// startAngle and endAngle is greater than or equal to 2pi, a complete circle is produced with no chord or radial line(s) drawn from the center. +// A closureType of "PIE" connects the end point to the start point by defining two straight line segments first from the end point to the center and then +// the center to the start point. A closureType of "CHORD" connects the end point to the start point by defining a straight line segment from the end point +// to the start point. Textures are applied individually to each face of the ArcClose2D. On the front (+Z) and back (-Z) faces of the ArcClose2D, when +// viewed from the +Z-axis, the texture is mapped onto each face with the same orientation as if the image were displayed normally in 2D. +void X3DImporter::readArcClose2D(XmlNode &node) { + std::string def, use; + std::string closureType("PIE"); + float endAngle = AI_MATH_HALF_PI_F; + float radius = 1; + bool solid = false; + float startAngle = 0; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getStdStrAttribute(node, "closureType", closureType); + XmlParser::getFloatAttribute(node, "endAngle", endAngle); + XmlParser::getFloatAttribute(node, "endAngle", endAngle); + XmlParser::getFloatAttribute(node, "radius", radius); + XmlParser::getBoolAttribute(node, "solid", solid); + XmlParser::getFloatAttribute(node, "startAngle", startAngle); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_ArcClose2D, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_ArcClose2D, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementGeometry2D *)ne)->Solid = solid; + // create point list of geometry object. + X3DGeoHelper::make_arc2D(startAngle, endAngle, radius, 10, ((X3DNodeElementGeometry2D *)ne)->Vertices); ///TODO: IME - AI_CONFIG for NumSeg + // add chord or two radiuses only if not a circle was defined + if (!((std::fabs(endAngle - startAngle) >= AI_MATH_TWO_PI_F) || (endAngle == startAngle))) { + std::list &vlist = ((X3DNodeElementGeometry2D *)ne)->Vertices; // just short alias. + + if ((closureType == "PIE") || (closureType == "\"PIE\"")) + vlist.push_back(aiVector3D(0, 0, 0)); // center point - first radial line + else if ((closureType != "CHORD") && (closureType != "\"CHORD\"")) + Throw_IncorrectAttrValue("ArcClose2D", "closureType"); + + vlist.push_back(*vlist.begin()); // arc first point - chord from first to last point of arc(if CHORD) or second radial line(if PIE). + } + + ((X3DNodeElementGeometry2D *)ne)->NumIndices = ((X3DNodeElementGeometry2D *)ne)->Vertices.size(); + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "ArcClose2D"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +void X3DImporter::readCircle2D(XmlNode &node) { + std::string def, use; + float radius = 1; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getFloatAttribute(node, "radius", radius); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Circle2D, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_Circle2D, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // create point list of geometry object and convert it to line set. + std::list tlist; + + X3DGeoHelper::make_arc2D(0, 0, radius, 10, tlist); ///TODO: IME - AI_CONFIG for NumSeg + X3DGeoHelper::extend_point_to_line(tlist, ((X3DNodeElementGeometry2D *)ne)->Vertices); + ((X3DNodeElementGeometry2D *)ne)->NumIndices = 2; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Circle2D"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +// The Disk2D node specifies a circular disk which is centred at (0, 0) in the local coordinate system. The outerRadius field specifies the radius of the +// outer dimension of the Disk2D. The innerRadius field specifies the inner dimension of the Disk2D. The value of outerRadius shall be greater than zero. +// The value of innerRadius shall be greater than or equal to zero and less than or equal to outerRadius. If innerRadius is zero, the Disk2D is completely +// filled. Otherwise, the area within the innerRadius forms a hole in the Disk2D. If innerRadius is equal to outerRadius, a solid circular line shall +// be drawn using the current line properties. Textures are applied individually to each face of the Disk2D. On the front (+Z) and back (-Z) faces of +// the Disk2D, when viewed from the +Z-axis, the texture is mapped onto each face with the same orientation as if the image were displayed normally in 2D. +void X3DImporter::readDisk2D(XmlNode &node) { + std::string def, use; + float innerRadius = 0; + float outerRadius = 1; + bool solid = false; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getFloatAttribute(node, "innerRadius", innerRadius); + XmlParser::getFloatAttribute(node, "outerRadius", outerRadius); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Disk2D, ne); + } else { + std::list tlist_o, tlist_i; + + if (innerRadius > outerRadius) Throw_IncorrectAttrValue("Disk2D", "innerRadius"); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_Disk2D, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // create point list of geometry object. + ///TODO: IME - AI_CONFIG for NumSeg + X3DGeoHelper::make_arc2D(0, 0, outerRadius, 10, tlist_o); // outer circle + if (innerRadius == 0.0f) { // make filled disk + // in tlist_o we already have points of circle. just copy it and sign as polygon. + ((X3DNodeElementGeometry2D *)ne)->Vertices = tlist_o; + ((X3DNodeElementGeometry2D *)ne)->NumIndices = tlist_o.size(); + } else if (innerRadius == outerRadius) { // make circle + // in tlist_o we already have points of circle. convert it to line set. + X3DGeoHelper::extend_point_to_line(tlist_o, ((X3DNodeElementGeometry2D *)ne)->Vertices); + ((X3DNodeElementGeometry2D *)ne)->NumIndices = 2; + } else { // make disk + std::list &vlist = ((X3DNodeElementGeometry2D *)ne)->Vertices; // just short alias. + + X3DGeoHelper::make_arc2D(0, 0, innerRadius, 10, tlist_i); // inner circle + // + // create quad list from two point lists + // + if (tlist_i.size() < 2) throw DeadlyImportError("Disk2D. Not enough points for creating quad list."); // tlist_i and tlist_o has equal size. + + // add all quads except last + for (std::list::iterator it_i = tlist_i.begin(), it_o = tlist_o.begin(); it_i != tlist_i.end();) { + // do not forget - CCW direction + vlist.push_back(*it_i++); // 1st point + vlist.push_back(*it_o++); // 2nd point + vlist.push_back(*it_o); // 3rd point + vlist.push_back(*it_i); // 4th point + } + + // add last quad + vlist.push_back(*tlist_i.end()); // 1st point + vlist.push_back(*tlist_o.end()); // 2nd point + vlist.push_back(*tlist_o.begin()); // 3rd point + vlist.push_back(*tlist_o.begin()); // 4th point + + ((X3DNodeElementGeometry2D *)ne)->NumIndices = 4; + } + + ((X3DNodeElementGeometry2D *)ne)->Solid = solid; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Disk2D"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +void X3DImporter::readPolyline2D(XmlNode &node) { + std::string def, use; + std::list lineSegments; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector2DListAttribute(node, "lineSegments", lineSegments); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Polyline2D, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_Polyline2D, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // + // convert read point list of geometry object to line set. + // + std::list tlist; + + // convert vec2 to vec3 + for (std::list::iterator it2 = lineSegments.begin(); it2 != lineSegments.end(); ++it2) + tlist.push_back(aiVector3D(it2->x, it2->y, 0)); + + // convert point set to line set + X3DGeoHelper::extend_point_to_line(tlist, ((X3DNodeElementGeometry2D *)ne)->Vertices); + ((X3DNodeElementGeometry2D *)ne)->NumIndices = 2; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Polyline2D"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +void X3DImporter::readPolypoint2D(XmlNode &node) { + std::string def, use; + std::list point; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector2DListAttribute(node, "point", point); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Polypoint2D, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_Polypoint2D, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // convert vec2 to vec3 + for (std::list::iterator it2 = point.begin(); it2 != point.end(); ++it2) { + ((X3DNodeElementGeometry2D *)ne)->Vertices.push_back(aiVector3D(it2->x, it2->y, 0)); + } + + ((X3DNodeElementGeometry2D *)ne)->NumIndices = 1; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Polypoint2D"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +void X3DImporter::readRectangle2D(XmlNode &node) { + std::string def, use; + aiVector2D size(2, 2); + bool solid = false; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector2DAttribute(node, "size", size); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Rectangle2D, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_Rectangle2D, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + float x1 = -size.x / 2.0f; + float x2 = size.x / 2.0f; + float y1 = -size.y / 2.0f; + float y2 = size.y / 2.0f; + std::list &vlist = ((X3DNodeElementGeometry2D *)ne)->Vertices; // just short alias. + + vlist.push_back(aiVector3D(x2, y1, 0)); // 1st point + vlist.push_back(aiVector3D(x2, y2, 0)); // 2nd point + vlist.push_back(aiVector3D(x1, y2, 0)); // 3rd point + vlist.push_back(aiVector3D(x1, y1, 0)); // 4th point + ((X3DNodeElementGeometry2D *)ne)->Solid = solid; + ((X3DNodeElementGeometry2D *)ne)->NumIndices = 4; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Rectangle2D"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +void X3DImporter::readTriangleSet2D(XmlNode &node) { + std::string def, use; + bool solid = false; + std::list vertices; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector2DListAttribute(node, "vertices", vertices); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TriangleSet2D, ne); + } else { + if (vertices.size() % 3) throw DeadlyImportError("TriangleSet2D. Not enough points for defining triangle."); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_TriangleSet2D, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // convert vec2 to vec3 + for (std::list::iterator it2 = vertices.begin(); it2 != vertices.end(); ++it2) { + ((X3DNodeElementGeometry2D *)ne)->Vertices.push_back(aiVector3D(it2->x, it2->y, 0)); + } + + ((X3DNodeElementGeometry2D *)ne)->Solid = solid; + ((X3DNodeElementGeometry2D *)ne)->NumIndices = 3; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "TriangleSet2D"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/code/AssetLib/X3D/X3DImporter_Geometry3D.cpp b/code/AssetLib/X3D/X3DImporter_Geometry3D.cpp new file mode 100644 index 000000000..4250bfb00 --- /dev/null +++ b/code/AssetLib/X3D/X3DImporter_Geometry3D.cpp @@ -0,0 +1,981 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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. + +---------------------------------------------------------------------- +*/ +/// \file X3DImporter_Geometry3D.cpp +/// \brief Parsing data from nodes of "Geometry3D" set of X3D. +/// \date 2015-2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" +#include "X3DXmlHelper.h" +#include "X3DGeoHelper.h" + +// Header files, Assimp. +#include + +namespace Assimp +{ + +// +// The Box node specifies a rectangular parallelepiped box centred at (0, 0, 0) in the local coordinate system and aligned with the local coordinate axes. +// By default, the box measures 2 units in each dimension, from -1 to +1. The size field specifies the extents of the box along the X-, Y-, and Z-axes +// respectively and each component value shall be greater than zero. +void X3DImporter::readBox(XmlNode &node) { + std::string def, use; + bool solid = true; + aiVector3D size(2, 2, 2); + X3DNodeElementBase* ne( nullptr ); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector3DAttribute(node, "size", size); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if(!use.empty()) + { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Box, ne); + } + else + { + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry3D(X3DElemType::ENET_Box, mNodeElementCur); + if(!def.empty()) ne->ID = def; + + X3DGeoHelper::rect_parallel_epiped(size, ((X3DNodeElementGeometry3D *)ne)->Vertices); // get quad list + ((X3DNodeElementGeometry3D *)ne)->Solid = solid; + ((X3DNodeElementGeometry3D *)ne)->NumIndices = 4; + // check for X3DMetadataObject childs. + if(!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Box"); + else + mNodeElementCur->Children.push_back(ne);// add made object as child to current element + + NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph + }// if(!use.empty()) else +} + +// +void X3DImporter::readCone(XmlNode &node) { + std::string use, def; + bool bottom = true; + float bottomRadius = 1; + float height = 2; + bool side = true; + bool solid = true; + X3DNodeElementBase* ne( nullptr ); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "solid", solid); + XmlParser::getBoolAttribute(node, "side", side); + XmlParser::getBoolAttribute(node, "bottom", bottom); + XmlParser::getFloatAttribute(node, "height", height); + XmlParser::getFloatAttribute(node, "bottomRadius", bottomRadius); + + // if "USE" defined then find already defined element. + if(!use.empty()) + { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Cone, ne); + } + else + { + const unsigned int tess = 30;///TODO: IME tessellation factor through ai_property + + std::vector tvec;// temp array for vertices. + + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry3D(X3DElemType::ENET_Cone, mNodeElementCur); + if(!def.empty()) ne->ID = def; + + // make cone or parts according to flags. + if(side) + { + StandardShapes::MakeCone(height, 0, bottomRadius, tess, tvec, !bottom); + } + else if(bottom) + { + StandardShapes::MakeCircle(bottomRadius, tess, tvec); + height = -(height / 2); + for(std::vector::iterator it = tvec.begin(); it != tvec.end(); ++it) it->y = height;// y - because circle made in oXZ. + } + + // copy data from temp array + for (std::vector::iterator it = tvec.begin(); it != tvec.end(); ++it) + ((X3DNodeElementGeometry3D*)ne)->Vertices.push_back(*it); + + ((X3DNodeElementGeometry3D *)ne)->Solid = solid; + ((X3DNodeElementGeometry3D *)ne)->NumIndices = 3; + // check for X3DMetadataObject childs. + if(!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Cone"); + else + mNodeElementCur->Children.push_back(ne);// add made object as child to current element + + NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph + }// if(!use.empty()) else +} + +// +void X3DImporter::readCylinder(XmlNode &node) { + std::string use, def; + bool bottom = true; + float height = 2; + float radius = 1; + bool side = true; + bool solid = true; + bool top = true; + X3DNodeElementBase* ne( nullptr ); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getFloatAttribute(node, "radius", radius); + XmlParser::getBoolAttribute(node, "solid", solid); + XmlParser::getBoolAttribute(node, "bottom", bottom); + XmlParser::getBoolAttribute(node, "top", top); + XmlParser::getBoolAttribute(node, "side", side); + XmlParser::getFloatAttribute(node, "height", height); + + // if "USE" defined then find already defined element. + if(!use.empty()) + { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Cylinder, ne); + } + else + { + const unsigned int tess = 30;///TODO: IME tessellation factor through ai_property + + std::vector tside;// temp array for vertices of side. + std::vector tcir;// temp array for vertices of circle. + + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry3D(X3DElemType::ENET_Cylinder, mNodeElementCur); + if(!def.empty()) ne->ID = def; + + // make cilynder or parts according to flags. + if(side) StandardShapes::MakeCone(height, radius, radius, tess, tside, true); + + height /= 2;// height defined for whole cylinder, when creating top and bottom circle we are using just half of height. + if(top || bottom) StandardShapes::MakeCircle(radius, tess, tcir); + // copy data from temp arrays + std::list &vlist = ((X3DNodeElementGeometry3D *)ne)->Vertices; // just short alias. + + for(std::vector::iterator it = tside.begin(); it != tside.end(); ++it) vlist.push_back(*it); + + if(top) + { + for(std::vector::iterator it = tcir.begin(); it != tcir.end(); ++it) + { + (*it).y = height;// y - because circle made in oXZ. + vlist.push_back(*it); + } + }// if(top) + + if(bottom) + { + for(std::vector::iterator it = tcir.begin(); it != tcir.end(); ++it) + { + (*it).y = -height;// y - because circle made in oXZ. + vlist.push_back(*it); + } + }// if(top) + + ((X3DNodeElementGeometry3D *)ne)->Solid = solid; + ((X3DNodeElementGeometry3D *)ne)->NumIndices = 3; + // check for X3DMetadataObject childs. + if(!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Cylinder"); + else + mNodeElementCur->Children.push_back(ne);// add made object as child to current element + + NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph + }// if(!use.empty()) else +} + +// +// +// ColorNormalTexCoordContentModel can contain Color (or ColorRGBA), Normal and TextureCoordinate, in any order. No more than one instance of any single +// node type is allowed. A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +// The ElevationGrid node specifies a uniform rectangular grid of varying height in the Y=0 plane of the local coordinate system. The geometry is described +// by a scalar array of height values that specify the height of a surface above each point of the grid. The xDimension and zDimension fields indicate +// the number of elements of the grid height array in the X and Z directions. Both xDimension and zDimension shall be greater than or equal to zero. +// If either the xDimension or the zDimension is less than two, the ElevationGrid contains no quadrilaterals. +void X3DImporter::readElevationGrid(XmlNode &node) { + std::string use, def; + bool ccw = true; + bool colorPerVertex = true; + float creaseAngle = 0; + std::vector height; + bool normalPerVertex = true; + bool solid = true; + int32_t xDimension = 0; + float xSpacing = 1; + int32_t zDimension = 0; + float zSpacing = 1; + X3DNodeElementBase* ne( nullptr ); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "solid", solid); + XmlParser::getBoolAttribute(node, "ccw", ccw); + XmlParser::getBoolAttribute(node, "colorPerVertex", colorPerVertex); + XmlParser::getBoolAttribute(node, "normalPerVertex", normalPerVertex); + XmlParser::getFloatAttribute(node, "creaseAngle", creaseAngle); + X3DXmlHelper::getFloatArrayAttribute(node, "height", height); + XmlParser::getIntAttribute(node, "xDimension", xDimension); + XmlParser::getFloatAttribute(node, "xSpacing", xSpacing); + XmlParser::getIntAttribute(node, "zDimension", zDimension); + XmlParser::getFloatAttribute(node, "zSpacing", zSpacing); + + // if "USE" defined then find already defined element. + if(!use.empty()) + { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_ElevationGrid, ne); + } + else + { + if((xSpacing == 0.0f) || (zSpacing == 0.0f)) throw DeadlyImportError("Spacing in must be grater than zero."); + if((xDimension <= 0) || (zDimension <= 0)) throw DeadlyImportError("Dimension in must be grater than zero."); + if ((size_t)(xDimension * zDimension) != height.size()) DeadlyImportError("Heights count must be equal to \"xDimension * zDimension\" in "); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementElevationGrid(X3DElemType::ENET_ElevationGrid, mNodeElementCur); + if(!def.empty()) ne->ID = def; + + X3DNodeElementElevationGrid &grid_alias = *((X3DNodeElementElevationGrid *)ne); // create alias for conveience + + {// create grid vertices list + std::vector::const_iterator he_it = height.begin(); + + for(int32_t zi = 0; zi < zDimension; zi++)// rows + { + for(int32_t xi = 0; xi < xDimension; xi++)// columns + { + aiVector3D tvec(xSpacing * xi, *he_it, zSpacing * zi); + + grid_alias.Vertices.push_back(tvec); + ++he_it; + } + } + }// END: create grid vertices list + // + // create faces list. In "coordIdx" format + // + // check if we have quads + if((xDimension < 2) || (zDimension < 2))// only one element in dimension is set, create line set. + { + ((X3DNodeElementElevationGrid *)ne)->NumIndices = 2; // will be holded as line set. + for(size_t i = 0, i_e = (grid_alias.Vertices.size() - 1); i < i_e; i++) + { + grid_alias.CoordIdx.push_back(static_cast(i)); + grid_alias.CoordIdx.push_back(static_cast(i + 1)); + grid_alias.CoordIdx.push_back(-1); + } + } + else// two or more elements in every dimension is set. create quad set. + { + ((X3DNodeElementElevationGrid *)ne)->NumIndices = 4; + for(int32_t fzi = 0, fzi_e = (zDimension - 1); fzi < fzi_e; fzi++)// rows + { + for(int32_t fxi = 0, fxi_e = (xDimension - 1); fxi < fxi_e; fxi++)// columns + { + // points direction in face. + if(ccw) + { + // CCW: + // 3 2 + // 0 1 + grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + fxi); + grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + (fxi + 1)); + grid_alias.CoordIdx.push_back(fzi * xDimension + (fxi + 1)); + grid_alias.CoordIdx.push_back(fzi * xDimension + fxi); + } + else + { + // CW: + // 0 1 + // 3 2 + grid_alias.CoordIdx.push_back(fzi * xDimension + fxi); + grid_alias.CoordIdx.push_back(fzi * xDimension + (fxi + 1)); + grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + (fxi + 1)); + grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + fxi); + }// if(ccw) else + + grid_alias.CoordIdx.push_back(-1); + }// for(int32_t fxi = 0, fxi_e = (xDimension - 1); fxi < fxi_e; fxi++) + }// for(int32_t fzi = 0, fzi_e = (zDimension - 1); fzi < fzi_e; fzi++) + }// if((xDimension < 2) || (zDimension < 2)) else + + grid_alias.ColorPerVertex = colorPerVertex; + grid_alias.NormalPerVertex = normalPerVertex; + grid_alias.CreaseAngle = creaseAngle; + grid_alias.Solid = solid; + // check for child nodes + if(!isNodeEmpty(node)) + { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") readColorRGBA(currentChildNode); + else if (currentChildName == "Normal") readNormal(currentChildNode); + else if (currentChildName == "TextureCoordinate") readTextureCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) skipUnsupportedNode("ElevationGrid", currentChildNode); + } + ParseHelper_Node_Exit(); + }// if(!mReader->isEmptyElement()) + else + { + mNodeElementCur->Children.push_back(ne);// add made object as child to current element + }// if(!mReader->isEmptyElement()) else + + NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph + }// if(!use.empty()) else +} + +template +static void GeometryHelper_Extrusion_CurveIsClosed(std::vector& pCurve, const bool pDropTail, const bool pRemoveLastPoint, bool& pCurveIsClosed) +{ + size_t cur_sz = pCurve.size(); + + pCurveIsClosed = false; + // for curve with less than four points checking is have no sense, + if(cur_sz < 4) return; + + for(size_t s = 3, s_e = cur_sz; s < s_e; s++) + { + // search for first point of duplicated part. + if(pCurve[0] == pCurve[s]) + { + bool found = true; + + // check if tail(indexed by b2) is duplicate of head(indexed by b1). + for(size_t b1 = 1, b2 = (s + 1); b2 < cur_sz; b1++, b2++) + { + if(pCurve[b1] != pCurve[b2]) + {// points not match: clear flag and break loop. + found = false; + + break; + } + }// for(size_t b1 = 1, b2 = (s + 1); b2 < cur_sz; b1++, b2++) + + // if duplicate tail is found then drop or not it depending on flags. + if(found) + { + pCurveIsClosed = true; + if(pDropTail) + { + if(!pRemoveLastPoint) s++;// prepare value for iterator's arithmetics. + + pCurve.erase(pCurve.begin() + s, pCurve.end());// remove tail + } + + break; + }// if(found) + }// if(pCurve[0] == pCurve[s]) + }// for(size_t s = 3, s_e = (cur_sz - 1); s < s_e; s++) +} + +static aiVector3D GeometryHelper_Extrusion_GetNextY(const size_t pSpine_PointIdx, const std::vector& pSpine, const bool pSpine_Closed) +{ + const size_t spine_idx_last = pSpine.size() - 1; + aiVector3D tvec; + + if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last))// at first special cases + { + if(pSpine_Closed) + {// If the spine curve is closed: The SCP for the first and last points is the same and is found using (spine[1] - spine[n - 2]) to compute the Y-axis. + // As we even for closed spine curve last and first point in pSpine are not the same: duplicates(spine[n - 1] which are equivalent to spine[0]) + // in tail are removed. + // So, last point in pSpine is a spine[n - 2] + tvec = pSpine[1] - pSpine[spine_idx_last]; + } + else if(pSpine_PointIdx == 0) + {// The Y-axis used for the first point is the vector from spine[0] to spine[1] + tvec = pSpine[1] - pSpine[0]; + } + else + {// The Y-axis used for the last point it is the vector from spine[n-2] to spine[n-1]. In our case(see above about dropping tail) spine[n - 1] is + // the spine[0]. + tvec = pSpine[spine_idx_last] - pSpine[spine_idx_last - 1]; + } + }// if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last)) + else + {// For all points other than the first or last: The Y-axis for spine[i] is found by normalizing the vector defined by (spine[i+1] - spine[i-1]). + tvec = pSpine[pSpine_PointIdx + 1] - pSpine[pSpine_PointIdx - 1]; + }// if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last)) else + + return tvec.Normalize(); +} + +static aiVector3D GeometryHelper_Extrusion_GetNextZ(const size_t pSpine_PointIdx, const std::vector& pSpine, const bool pSpine_Closed, + const aiVector3D pVecZ_Prev) +{ + const aiVector3D zero_vec(0); + const size_t spine_idx_last = pSpine.size() - 1; + + aiVector3D tvec; + + // at first special cases + if(pSpine.size() < 3)// spine have not enough points for vector calculations. + { + tvec.Set(0, 0, 1); + } + else if(pSpine_PointIdx == 0)// special case: first point + { + if(pSpine_Closed)// for calculating use previous point in curve s[n - 2]. In list it's a last point, because point s[n - 1] was removed as duplicate. + { + tvec = (pSpine[1] - pSpine[0]) ^ (pSpine[spine_idx_last] - pSpine[0]); + } + else // for not closed curve first and next point(s[0] and s[1]) has the same vector Z. + { + bool found = false; + + // As said: "If the Z-axis of the first point is undefined (because the spine is not closed and the first two spine segments are collinear) + // then the Z-axis for the first spine point with a defined Z-axis is used." + // Walk through spine and find Z. + for(size_t next_point = 2; (next_point <= spine_idx_last) && !found; next_point++) + { + // (pSpine[2] - pSpine[1]) ^ (pSpine[0] - pSpine[1]) + tvec = (pSpine[next_point] - pSpine[next_point - 1]) ^ (pSpine[next_point - 2] - pSpine[next_point - 1]); + found = !tvec.Equal(zero_vec); + } + + // if entire spine are collinear then use OZ axis. + if(!found) tvec.Set(0, 0, 1); + }// if(pSpine_Closed) else + }// else if(pSpine_PointIdx == 0) + else if(pSpine_PointIdx == spine_idx_last)// special case: last point + { + if(pSpine_Closed) + {// do not forget that real last point s[n - 1] is removed as duplicated. And in this case we are calculating vector Z for point s[n - 2]. + tvec = (pSpine[0] - pSpine[pSpine_PointIdx]) ^ (pSpine[pSpine_PointIdx - 1] - pSpine[pSpine_PointIdx]); + // if taken spine vectors are collinear then use previous vector Z. + if(tvec.Equal(zero_vec)) tvec = pVecZ_Prev; + } + else + {// vector Z for last point of not closed curve is previous vector Z. + tvec = pVecZ_Prev; + } + } + else// regular point + { + tvec = (pSpine[pSpine_PointIdx + 1] - pSpine[pSpine_PointIdx]) ^ (pSpine[pSpine_PointIdx - 1] - pSpine[pSpine_PointIdx]); + // if taken spine vectors are collinear then use previous vector Z. + if(tvec.Equal(zero_vec)) tvec = pVecZ_Prev; + } + + // After determining the Z-axis, its dot product with the Z-axis of the previous spine point is computed. If this value is negative, the Z-axis + // is flipped (multiplied by -1). + if((tvec * pVecZ_Prev) < 0) tvec = -tvec; + + return tvec.Normalize(); +} + +// +void X3DImporter::readExtrusion(XmlNode &node) { + std::string use, def; + bool beginCap = true; + bool ccw = true; + bool convex = true; + float creaseAngle = 0; + std::vector crossSection; + bool endCap = true; + std::vector orientation; + std::vector scale; + bool solid = true; + std::vector spine; + X3DNodeElementBase* ne( nullptr ); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "beginCap", beginCap); + XmlParser::getBoolAttribute(node, "ccw", ccw); + XmlParser::getBoolAttribute(node, "convex", convex); + XmlParser::getFloatAttribute(node, "creaseAngle", creaseAngle); + X3DXmlHelper::getVector2DArrayAttribute(node, "crossSection", crossSection); + XmlParser::getBoolAttribute(node, "endCap", endCap); + X3DXmlHelper::getFloatArrayAttribute(node, "orientation", orientation); + X3DXmlHelper::getVector2DArrayAttribute(node, "scale", scale); + XmlParser::getBoolAttribute(node, "solid", solid); + X3DXmlHelper::getVector3DArrayAttribute(node, "spine", spine); + + // if "USE" defined then find already defined element. + if(!use.empty()) + { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Extrusion, ne); + } + else + { + // + // check if default values must be assigned + // + if(spine.size() == 0) + { + spine.resize(2); + spine[0].Set(0, 0, 0), spine[1].Set(0, 1, 0); + } + else if(spine.size() == 1) + { + throw DeadlyImportError("ParseNode_Geometry3D_Extrusion. Spine must have at least two points."); + } + + if(crossSection.size() == 0) + { + crossSection.resize(5); + crossSection[0].Set(1, 1), crossSection[1].Set(1, -1), crossSection[2].Set(-1, -1), crossSection[3].Set(-1, 1), crossSection[4].Set(1, 1); + } + + {// orientation + size_t ori_size = orientation.size() / 4; + + if(ori_size < spine.size()) + { + float add_ori[4];// values that will be added + + if(ori_size == 1)// if "orientation" has one element(means one MFRotation with four components) then use it value for all spine points. + { + add_ori[0] = orientation[0], add_ori[1] = orientation[1], add_ori[2] = orientation[2], add_ori[3] = orientation[3]; + } + else// else - use default values + { + add_ori[0] = 0, add_ori[1] = 0, add_ori[2] = 1, add_ori[3] = 0; + } + + orientation.reserve(spine.size() * 4); + for(size_t i = 0, i_e = (spine.size() - ori_size); i < i_e; i++) + orientation.push_back(add_ori[0]), orientation.push_back(add_ori[1]), orientation.push_back(add_ori[2]), orientation.push_back(add_ori[3]); + } + + if(orientation.size() % 4) throw DeadlyImportError("Attribute \"orientation\" in must has multiple four quantity of numbers."); + }// END: orientation + + {// scale + if(scale.size() < spine.size()) + { + aiVector2D add_sc; + + if(scale.size() == 1)// if "scale" has one element then use it value for all spine points. + add_sc = scale[0]; + else// else - use default values + add_sc.Set(1, 1); + + scale.reserve(spine.size()); + for(size_t i = 0, i_e = (spine.size() - scale.size()); i < i_e; i++) scale.push_back(add_sc); + } + }// END: scale + // + // create and if needed - define new geometry object. + // + ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_Extrusion, mNodeElementCur); + if(!def.empty()) ne->ID = def; + + X3DNodeElementIndexedSet &ext_alias = *((X3DNodeElementIndexedSet *)ne); // create alias for conveience + // assign part of input data + ext_alias.CCW = ccw; + ext_alias.Convex = convex; + ext_alias.CreaseAngle = creaseAngle; + ext_alias.Solid = solid; + + // + // How we done it at all? + // 1. At first we will calculate array of basises for every point in spine(look SCP in ISO-dic). Also "orientation" vector + // are applied vor every basis. + // 2. After that we can create array of point sets: which are scaled, transferred to basis of relative basis and at final translated to real position + // using relative spine point. + // 3. Next step is creating CoordIdx array(do not forget "-1" delimiter). While creating CoordIdx also created faces for begin and end caps, if + // needed. While createing CootdIdx is taking in account CCW flag. + // 4. The last step: create Vertices list. + // + bool spine_closed;// flag: true if spine curve is closed. + bool cross_closed;// flag: true if cross curve is closed. + std::vector basis_arr;// array of basises. ROW_a - X, ROW_b - Y, ROW_c - Z. + std::vector > pointset_arr;// array of point sets: cross curves. + + // detect closed curves + GeometryHelper_Extrusion_CurveIsClosed(crossSection, true, true, cross_closed);// true - drop tail, true - remove duplicate end. + GeometryHelper_Extrusion_CurveIsClosed(spine, true, true, spine_closed);// true - drop tail, true - remove duplicate end. + // If both cap are requested and spine curve is closed then we can make only one cap. Because second cap will be the same surface. + if(spine_closed) + { + beginCap |= endCap; + endCap = false; + } + + {// 1. Calculate array of basises. + aiMatrix4x4 rotmat; + aiVector3D vecX(0), vecY(0), vecZ(0); + + basis_arr.resize(spine.size()); + for(size_t i = 0, i_e = spine.size(); i < i_e; i++) + { + aiVector3D tvec; + + // get axises of basis. + vecY = GeometryHelper_Extrusion_GetNextY(i, spine, spine_closed); + vecZ = GeometryHelper_Extrusion_GetNextZ(i, spine, spine_closed, vecZ); + vecX = (vecY ^ vecZ).Normalize(); + // get rotation matrix and apply "orientation" to basis + aiMatrix4x4::Rotation(orientation[i * 4 + 3], aiVector3D(orientation[i * 4], orientation[i * 4 + 1], orientation[i * 4 + 2]), rotmat); + tvec = vecX, tvec *= rotmat, basis_arr[i].a1 = tvec.x, basis_arr[i].a2 = tvec.y, basis_arr[i].a3 = tvec.z; + tvec = vecY, tvec *= rotmat, basis_arr[i].b1 = tvec.x, basis_arr[i].b2 = tvec.y, basis_arr[i].b3 = tvec.z; + tvec = vecZ, tvec *= rotmat, basis_arr[i].c1 = tvec.x, basis_arr[i].c2 = tvec.y, basis_arr[i].c3 = tvec.z; + }// for(size_t i = 0, i_e = spine.size(); i < i_e; i++) + }// END: 1. Calculate array of basises + + {// 2. Create array of point sets. + aiMatrix4x4 scmat; + std::vector tcross(crossSection.size()); + + pointset_arr.resize(spine.size()); + for(size_t spi = 0, spi_e = spine.size(); spi < spi_e; spi++) + { + aiVector3D tc23vec; + + tc23vec.Set(scale[spi].x, 0, scale[spi].y); + aiMatrix4x4::Scaling(tc23vec, scmat); + for(size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; cri++) + { + aiVector3D tvecX, tvecY, tvecZ; + + tc23vec.Set(crossSection[cri].x, 0, crossSection[cri].y); + // apply scaling to point + tcross[cri] = scmat * tc23vec; + // + // transfer point to new basis + // calculate coordinate in new basis + tvecX.Set(basis_arr[spi].a1, basis_arr[spi].a2, basis_arr[spi].a3), tvecX *= tcross[cri].x; + tvecY.Set(basis_arr[spi].b1, basis_arr[spi].b2, basis_arr[spi].b3), tvecY *= tcross[cri].y; + tvecZ.Set(basis_arr[spi].c1, basis_arr[spi].c2, basis_arr[spi].c3), tvecZ *= tcross[cri].z; + // apply new coordinates and translate it to spine point. + tcross[cri] = tvecX + tvecY + tvecZ + spine[spi]; + }// for(size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; i++) + + pointset_arr[spi] = tcross;// store transferred point set + }// for(size_t spi = 0, spi_e = spine.size(); spi < spi_e; i++) + }// END: 2. Create array of point sets. + + {// 3. Create CoordIdx. + // add caps if needed + if(beginCap) + { + // add cap as polygon. vertices of cap are places at begin, so just add numbers from zero. + for(size_t i = 0, i_e = crossSection.size(); i < i_e; i++) ext_alias.CoordIndex.push_back(static_cast(i)); + + // add delimiter + ext_alias.CoordIndex.push_back(-1); + }// if(beginCap) + + if(endCap) + { + // add cap as polygon. vertices of cap are places at end, as for beginCap use just sequence of numbers but with offset. + size_t beg = (pointset_arr.size() - 1) * crossSection.size(); + + for(size_t i = beg, i_e = (beg + crossSection.size()); i < i_e; i++) ext_alias.CoordIndex.push_back(static_cast(i)); + + // add delimiter + ext_alias.CoordIndex.push_back(-1); + }// if(beginCap) + + // add quads + for(size_t spi = 0, spi_e = (spine.size() - 1); spi <= spi_e; spi++) + { + const size_t cr_sz = crossSection.size(); + const size_t cr_last = crossSection.size() - 1; + + size_t right_col;// hold index basis for points of quad placed in right column; + + if(spi != spi_e) + right_col = spi + 1; + else if(spine_closed)// if spine curve is closed then one more quad is needed: between first and last points of curve. + right_col = 0; + else + break;// if spine curve is not closed then break the loop, because spi is out of range for that type of spine. + + for(size_t cri = 0; cri < cr_sz; cri++) + { + if(cri != cr_last) + { + MACRO_FACE_ADD_QUAD(ccw, ext_alias.CoordIndex, + static_cast(spi * cr_sz + cri), + static_cast(right_col * cr_sz + cri), + static_cast(right_col * cr_sz + cri + 1), + static_cast(spi * cr_sz + cri + 1)); + // add delimiter + ext_alias.CoordIndex.push_back(-1); + } + else if(cross_closed)// if cross curve is closed then one more quad is needed: between first and last points of curve. + { + MACRO_FACE_ADD_QUAD(ccw, ext_alias.CoordIndex, + static_cast(spi * cr_sz + cri), + static_cast(right_col * cr_sz + cri), + static_cast(right_col * cr_sz + 0), + static_cast(spi * cr_sz + 0)); + // add delimiter + ext_alias.CoordIndex.push_back(-1); + } + }// for(size_t cri = 0; cri < cr_sz; cri++) + }// for(size_t spi = 0, spi_e = (spine.size() - 2); spi < spi_e; spi++) + }// END: 3. Create CoordIdx. + + {// 4. Create vertices list. + // just copy all vertices + for(size_t spi = 0, spi_e = spine.size(); spi < spi_e; spi++) + { + for(size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; cri++) + { + ext_alias.Vertices.push_back(pointset_arr[spi][cri]); + } + } + }// END: 4. Create vertices list. +//PrintVectorSet("Ext. CoordIdx", ext_alias.CoordIndex); +//PrintVectorSet("Ext. Vertices", ext_alias.Vertices); + // check for child nodes + if(!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Extrusion"); + else + mNodeElementCur->Children.push_back(ne);// add made object as child to current element + + NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph + }// if(!use.empty()) else +} + +// +// +// ComposedGeometryContentModel is the child-node content model corresponding to X3DComposedGeometryNodes. It can contain Color (or ColorRGBA), Coordinate, +// Normal and TextureCoordinate, in any order. No more than one instance of these nodes is allowed. Multiple VertexAttribute (FloatVertexAttribute, +// Matrix3VertexAttribute, Matrix4VertexAttribute) nodes can also be contained. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +void X3DImporter::readIndexedFaceSet(XmlNode &node) { + std::string use, def; + bool ccw = true; + std::vector colorIndex; + bool colorPerVertex = true; + bool convex = true; + std::vector coordIndex; + float creaseAngle = 0; + std::vector normalIndex; + bool normalPerVertex = true; + bool solid = true; + std::vector texCoordIndex; + X3DNodeElementBase* ne( nullptr ); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "ccw", ccw); + X3DXmlHelper::getInt32ArrayAttribute(node, "colorIndex", colorIndex); + XmlParser::getBoolAttribute(node, "colorPerVertex", colorPerVertex); + XmlParser::getBoolAttribute(node, "convex", convex); + X3DXmlHelper::getInt32ArrayAttribute(node, "coordIndex", coordIndex); + XmlParser::getFloatAttribute(node, "creaseAngle", creaseAngle); + X3DXmlHelper::getInt32ArrayAttribute(node, "normalIndex", normalIndex); + XmlParser::getBoolAttribute(node, "normalPerVertex", normalPerVertex); + XmlParser::getBoolAttribute(node, "solid", solid); + X3DXmlHelper::getInt32ArrayAttribute(node, "texCoordIndex", texCoordIndex); + + // if "USE" defined then find already defined element. + if(!use.empty()) + { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedFaceSet, ne); + } + else + { + // check data + if(coordIndex.size() == 0) throw DeadlyImportError("IndexedFaceSet must contain not empty \"coordIndex\" attribute."); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_IndexedFaceSet, mNodeElementCur); + if(!def.empty()) ne->ID = def; + + X3DNodeElementIndexedSet &ne_alias = *((X3DNodeElementIndexedSet *)ne); + + ne_alias.CCW = ccw; + ne_alias.ColorIndex = colorIndex; + ne_alias.ColorPerVertex = colorPerVertex; + ne_alias.Convex = convex; + ne_alias.CoordIndex = coordIndex; + ne_alias.CreaseAngle = creaseAngle; + ne_alias.NormalIndex = normalIndex; + ne_alias.NormalPerVertex = normalPerVertex; + ne_alias.Solid = solid; + ne_alias.TexCoordIndex = texCoordIndex; + // check for child nodes + if(!isNodeEmpty(node)) + { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") readCoordinate(currentChildNode); + else if (currentChildName == "Normal") readNormal(currentChildNode); + else if (currentChildName == "TextureCoordinate") readTextureCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) skipUnsupportedNode("IndexedFaceSet", currentChildNode); + } + ParseHelper_Node_Exit(); + }// if(!isNodeEmpty(node)) + else + { + mNodeElementCur->Children.push_back(ne);// add made object as child to current element + } + + NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph + }// if(!use.empty()) else +} + +// +void X3DImporter::readSphere(XmlNode &node) { + std::string use, def; + ai_real radius = 1; + bool solid = true; + X3DNodeElementBase* ne( nullptr ); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getFloatAttribute(node, "radius", radius); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if(!use.empty()) + { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Sphere, ne); + } + else + { + const unsigned int tess = 3;///TODO: IME tessellation factor through ai_property + + std::vector tlist; + + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry3D(X3DElemType::ENET_Sphere, mNodeElementCur); + if(!def.empty()) ne->ID = def; + + StandardShapes::MakeSphere(tess, tlist); + // copy data from temp array and apply scale + for(std::vector::iterator it = tlist.begin(); it != tlist.end(); ++it) + { + ((X3DNodeElementGeometry3D *)ne)->Vertices.push_back(*it * radius); + } + + ((X3DNodeElementGeometry3D *)ne)->Solid = solid; + ((X3DNodeElementGeometry3D *)ne)->NumIndices = 3; + // check for X3DMetadataObject childs. + if(!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Sphere"); + else + mNodeElementCur->Children.push_back(ne);// add made object as child to current element + + NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph + }// if(!use.empty()) else +} + +}// namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/code/AssetLib/X3D/X3DImporter_Group.cpp b/code/AssetLib/X3D/X3DImporter_Group.cpp new file mode 100644 index 000000000..d672d741b --- /dev/null +++ b/code/AssetLib/X3D/X3DImporter_Group.cpp @@ -0,0 +1,273 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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. + +---------------------------------------------------------------------- +*/ +/// \file X3DImporter_Group.cpp +/// \brief Parsing data from nodes of "Grouping" set of X3D. +/// date 2015-2016 +/// author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" +#include "X3DXmlHelper.h" + +namespace Assimp { + +// +// <\!-- ChildContentModel --> +// ChildContentModel is the child-node content model corresponding to X3DChildNode, combining all profiles. ChildContentModel can contain most nodes, +// other Grouping nodes, Prototype declarations and ProtoInstances in any order and any combination. When the assigned profile is less than Full, the +// precise palette of legal nodes that are available depends on assigned profile and components. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +// A Group node contains children nodes without introducing a new transformation. It is equivalent to a Transform node containing an identity transform. +void X3DImporter::startReadGroup(XmlNode &node) { + std::string def, use; + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + X3DNodeElementBase *ne; + + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); + } else { + ParseHelper_Group_Begin(); // create new grouping element and go deeper if node has children. + // at this place new group mode created and made current, so we can name it. + if (!def.empty()) mNodeElementCur->ID = def; + // in grouping set of nodes check X3DMetadataObject is not needed, because it is done in parser function. + + // for empty element exit from node in that place + if (isNodeEmpty(node)) ParseHelper_Node_Exit(); + } // if(!use.empty()) else +} + +void X3DImporter::endReadGroup() { + ParseHelper_Node_Exit(); // go up in scene graph +} + +// +// <\!-- ChildContentModel --> +// ChildContentModel is the child-node content model corresponding to X3DChildNode, combining all profiles. ChildContentModel can contain most nodes, +// other Grouping nodes, Prototype declarations and ProtoInstances in any order and any combination. When the assigned profile is less than Full, the +// precise palette of legal nodes that are available depends on assigned profile and components. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +// The StaticGroup node contains children nodes which cannot be modified. StaticGroup children are guaranteed to not change, send events, receive events or +// contain any USE references outside the StaticGroup. +void X3DImporter::startReadStaticGroup(XmlNode &node) { + std::string def, use; + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + X3DNodeElementBase *ne; + + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); + } else { + ParseHelper_Group_Begin(true); // create new grouping element and go deeper if node has children. + // at this place new group mode created and made current, so we can name it. + if (!def.empty()) mNodeElementCur->ID = def; + // in grouping set of nodes check X3DMetadataObject is not needed, because it is done in parser function. + + // for empty element exit from node in that place + if (isNodeEmpty(node)) ParseHelper_Node_Exit(); + } // if(!use.empty()) else +} + +void X3DImporter::endReadStaticGroup() { + ParseHelper_Node_Exit(); // go up in scene graph +} + +// +// <\!-- ChildContentModel --> +// ChildContentModel is the child-node content model corresponding to X3DChildNode, combining all profiles. ChildContentModel can contain most nodes, +// other Grouping nodes, Prototype declarations and ProtoInstances in any order and any combination. When the assigned profile is less than Full, the +// precise palette of legal nodes that are available depends on assigned profile and components. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +// The Switch grouping node traverses zero or one of the nodes specified in the children field. The whichChoice field specifies the index of the child +// to traverse, with the first child having index 0. If whichChoice is less than zero or greater than the number of nodes in the children field, nothing +// is chosen. +void X3DImporter::startReadSwitch(XmlNode &node) { + std::string def, use; + int32_t whichChoice = -1; + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getIntAttribute(node, "whichChoice", whichChoice); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + X3DNodeElementBase *ne; + + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); + } else { + ParseHelper_Group_Begin(); // create new grouping element and go deeper if node has children. + // at this place new group mode created and made current, so we can name it. + if (!def.empty()) mNodeElementCur->ID = def; + + // also set values specific to this type of group + ((X3DNodeElementGroup *)mNodeElementCur)->UseChoice = true; + ((X3DNodeElementGroup *)mNodeElementCur)->Choice = whichChoice; + // in grouping set of nodes check X3DMetadataObject is not needed, because it is done in parser function. + + // for empty element exit from node in that place + if (isNodeEmpty(node)) ParseHelper_Node_Exit(); + } // if(!use.empty()) else +} + +void X3DImporter::endReadSwitch() { + // just exit from node. Defined choice will be accepted at postprocessing stage. + ParseHelper_Node_Exit(); // go up in scene graph +} + +// +// <\!-- ChildContentModel --> +// ChildContentModel is the child-node content model corresponding to X3DChildNode, combining all profiles. ChildContentModel can contain most nodes, +// other Grouping nodes, Prototype declarations and ProtoInstances in any order and any combination. When the assigned profile is less than Full, the +// precise palette of legal nodes that are available depends on assigned profile and components. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +// The Transform node is a grouping node that defines a coordinate system for its children that is relative to the coordinate systems of its ancestors. +// Given a 3-dimensional point P and Transform node, P is transformed into point P' in its parent's coordinate system by a series of intermediate +// transformations. In matrix transformation notation, where C (center), SR (scaleOrientation), T (translation), R (rotation), and S (scale) are the +// equivalent transformation matrices, +// P' = T * C * R * SR * S * -SR * -C * P +void X3DImporter::startReadTransform(XmlNode &node) { + aiVector3D center(0, 0, 0); + float rotation[4] = { 0, 0, 1, 0 }; + aiVector3D scale(1, 1, 1); // A value of zero indicates that any child geometry shall not be displayed + float scale_orientation[4] = { 0, 0, 1, 0 }; + aiVector3D translation(0, 0, 0); + aiMatrix4x4 matr, tmatr; + std::string use, def; + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector3DAttribute(node, "center", center); + X3DXmlHelper::getVector3DAttribute(node, "scale", scale); + X3DXmlHelper::getVector3DAttribute(node, "translation", translation); + std::vector tvec; + if (X3DXmlHelper::getFloatArrayAttribute(node, "rotation", tvec)) { + if (tvec.size() != 4) throw DeadlyImportError(": rotation vector must have 4 elements."); + memcpy(rotation, tvec.data(), sizeof(rotation)); + tvec.clear(); + } + if (X3DXmlHelper::getFloatArrayAttribute(node, "scaleOrientation", tvec)) { + if (tvec.size() != 4) throw DeadlyImportError(": scaleOrientation vector must have 4 elements."); + memcpy(scale_orientation, tvec.data(), sizeof(scale_orientation)); + tvec.clear(); + } + + // if "USE" defined then find already defined element. + if (!use.empty()) { + X3DNodeElementBase *ne(nullptr); + + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); + } else { + ParseHelper_Group_Begin(); // create new grouping element and go deeper if node has children. + // at this place new group mode created and made current, so we can name it. + if (!def.empty()) { + mNodeElementCur->ID = def; + } + + // + // also set values specific to this type of group + // + // calculate transformation matrix + aiMatrix4x4::Translation(translation, matr); // T + aiMatrix4x4::Translation(center, tmatr); // C + matr *= tmatr; + aiMatrix4x4::Rotation(rotation[3], aiVector3D(rotation[0], rotation[1], rotation[2]), tmatr); // R + matr *= tmatr; + aiMatrix4x4::Rotation(scale_orientation[3], aiVector3D(scale_orientation[0], scale_orientation[1], scale_orientation[2]), tmatr); // SR + matr *= tmatr; + aiMatrix4x4::Scaling(scale, tmatr); // S + matr *= tmatr; + aiMatrix4x4::Rotation(-scale_orientation[3], aiVector3D(scale_orientation[0], scale_orientation[1], scale_orientation[2]), tmatr); // -SR + matr *= tmatr; + aiMatrix4x4::Translation(-center, tmatr); // -C + matr *= tmatr; + // and assign it + ((X3DNodeElementGroup *)mNodeElementCur)->Transformation = matr; + // in grouping set of nodes check X3DMetadataObject is not needed, because it is done in parser function. + + // for empty element exit from node in that place + if (isNodeEmpty(node)) { + ParseHelper_Node_Exit(); + } + } // if(!use.empty()) else +} + +void X3DImporter::endReadTransform() { + ParseHelper_Node_Exit(); // go up in scene graph +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/code/AssetLib/X3D/X3DImporter_Light.cpp b/code/AssetLib/X3D/X3DImporter_Light.cpp new file mode 100644 index 000000000..b9bd17164 --- /dev/null +++ b/code/AssetLib/X3D/X3DImporter_Light.cpp @@ -0,0 +1,270 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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. + +---------------------------------------------------------------------- +*/ +/// \file X3DImporter_Light.cpp +/// \brief Parsing data from nodes of "Lighting" set of X3D. +/// date 2015-2016 +/// author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" +#include "X3DXmlHelper.h" +#include + +namespace Assimp { + +// +void X3DImporter::readDirectionalLight(XmlNode &node) { + std::string def, use; + float ambientIntensity = 0; + aiColor3D color(1, 1, 1); + aiVector3D direction(0, 0, -1); + bool global = false; + float intensity = 1; + bool on = true; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getFloatAttribute(node, "ambientIntensity", ambientIntensity); + X3DXmlHelper::getColor3DAttribute(node, "color", color); + X3DXmlHelper::getVector3DAttribute(node, "direction", direction); + XmlParser::getBoolAttribute(node, "global", global); + XmlParser::getFloatAttribute(node, "intensity", intensity); + XmlParser::getBoolAttribute(node, "on", on); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_DirectionalLight, ne); + } else { + if (on) { + // create and if needed - define new geometry object. + ne = new X3DNodeElementLight(X3DElemType::ENET_DirectionalLight, mNodeElementCur); + if (!def.empty()) + ne->ID = def; + else + ne->ID = "DirectionalLight_" + ai_to_string((size_t)ne); // make random name + + ((X3DNodeElementLight *)ne)->AmbientIntensity = ambientIntensity; + ((X3DNodeElementLight *)ne)->Color = color; + ((X3DNodeElementLight *)ne)->Direction = direction; + ((X3DNodeElementLight *)ne)->Global = global; + ((X3DNodeElementLight *)ne)->Intensity = intensity; + // Assimp want a node with name similar to a light. "Why? I don't no." ) + ParseHelper_Group_Begin(false); + + mNodeElementCur->ID = ne->ID; // assign name to node and return to light element. + ParseHelper_Node_Exit(); + // check for child nodes + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "DirectionalLight"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(on) + } // if(!use.empty()) else +} + +// +void X3DImporter::readPointLight(XmlNode &node) { + std::string def, use; + float ambientIntensity = 0; + aiVector3D attenuation(1, 0, 0); + aiColor3D color(1, 1, 1); + bool global = true; + float intensity = 1; + aiVector3D location(0, 0, 0); + bool on = true; + float radius = 100; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getFloatAttribute(node, "ambientIntensity", ambientIntensity); + X3DXmlHelper::getVector3DAttribute(node, "attenuation", attenuation); + X3DXmlHelper::getColor3DAttribute(node, "color", color); + XmlParser::getBoolAttribute(node, "global", global); + XmlParser::getFloatAttribute(node, "intensity", intensity); + X3DXmlHelper::getVector3DAttribute(node, "location", location); + XmlParser::getBoolAttribute(node, "on", on); + XmlParser::getFloatAttribute(node, "radius", radius); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_PointLight, ne); + } else { + if (on) { + // create and if needed - define new geometry object. + ne = new X3DNodeElementLight(X3DElemType::ENET_PointLight, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementLight *)ne)->AmbientIntensity = ambientIntensity; + ((X3DNodeElementLight *)ne)->Attenuation = attenuation; + ((X3DNodeElementLight *)ne)->Color = color; + ((X3DNodeElementLight *)ne)->Global = global; + ((X3DNodeElementLight *)ne)->Intensity = intensity; + ((X3DNodeElementLight *)ne)->Location = location; + ((X3DNodeElementLight *)ne)->Radius = radius; + // Assimp want a node with name similar to a light. "Why? I don't no." ) + ParseHelper_Group_Begin(false); + // make random name + if (ne->ID.empty()) ne->ID = "PointLight_" + ai_to_string((size_t)ne); + + mNodeElementCur->ID = ne->ID; // assign name to node and return to light element. + ParseHelper_Node_Exit(); + // check for child nodes + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "PointLight"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(on) + } // if(!use.empty()) else +} + +// +void X3DImporter::readSpotLight(XmlNode &node) { + std::string def, use; + float ambientIntensity = 0; + aiVector3D attenuation(1, 0, 0); + float beamWidth = 0.7854f; + aiColor3D color(1, 1, 1); + float cutOffAngle = 1.570796f; + aiVector3D direction(0, 0, -1); + bool global = true; + float intensity = 1; + aiVector3D location(0, 0, 0); + bool on = true; + float radius = 100; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getFloatAttribute(node, "ambientIntensity", ambientIntensity); + X3DXmlHelper::getVector3DAttribute(node, "attenuation", attenuation); + XmlParser::getFloatAttribute(node, "beamWidth", beamWidth); + X3DXmlHelper::getColor3DAttribute(node, "color", color); + XmlParser::getFloatAttribute(node, "cutOffAngle", cutOffAngle); + X3DXmlHelper::getVector3DAttribute(node, "direction", direction); + XmlParser::getBoolAttribute(node, "global", global); + XmlParser::getFloatAttribute(node, "intensity", intensity); + X3DXmlHelper::getVector3DAttribute(node, "location", location); + XmlParser::getBoolAttribute(node, "on", on); + XmlParser::getFloatAttribute(node, "radius", radius); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_SpotLight, ne); + } else { + if (on) { + // create and if needed - define new geometry object. + ne = new X3DNodeElementLight(X3DElemType::ENET_SpotLight, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + if (beamWidth > cutOffAngle) beamWidth = cutOffAngle; + + ((X3DNodeElementLight *)ne)->AmbientIntensity = ambientIntensity; + ((X3DNodeElementLight *)ne)->Attenuation = attenuation; + ((X3DNodeElementLight *)ne)->BeamWidth = beamWidth; + ((X3DNodeElementLight *)ne)->Color = color; + ((X3DNodeElementLight *)ne)->CutOffAngle = cutOffAngle; + ((X3DNodeElementLight *)ne)->Direction = direction; + ((X3DNodeElementLight *)ne)->Global = global; + ((X3DNodeElementLight *)ne)->Intensity = intensity; + ((X3DNodeElementLight *)ne)->Location = location; + ((X3DNodeElementLight *)ne)->Radius = radius; + + // Assimp want a node with name similar to a light. "Why? I don't no." ) + ParseHelper_Group_Begin(false); + // make random name + if (ne->ID.empty()) ne->ID = "SpotLight_" + ai_to_string((size_t)ne); + + mNodeElementCur->ID = ne->ID; // assign name to node and return to light element. + ParseHelper_Node_Exit(); + // check for child nodes + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "SpotLight"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(on) + } // if(!use.empty()) else +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/code/AssetLib/X3D/X3DImporter_Macro.hpp b/code/AssetLib/X3D/X3DImporter_Macro.hpp new file mode 100644 index 000000000..8d902b346 --- /dev/null +++ b/code/AssetLib/X3D/X3DImporter_Macro.hpp @@ -0,0 +1,110 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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. + +---------------------------------------------------------------------- +*/ +/// \file X3DImporter_Macro.hpp +/// \brief Useful macrodefines. +/// \date 2015-2016 +/// \author smal.root@gmail.com + +#ifndef X3DIMPORTER_MACRO_HPP_INCLUDED +#define X3DIMPORTER_MACRO_HPP_INCLUDED + +/// \def MACRO_USE_CHECKANDAPPLY(pDEF, pUSE, pNE) +/// Used for regular checking while attribute "USE" is defined. +/// \param [in] pNode - pugi xml node to read. +/// \param [in] pDEF - string holding "DEF" value. +/// \param [in] pUSE - string holding "USE" value. +/// \param [in] pType - type of element to find. +/// \param [out] pNE - pointer to found node element. +#define MACRO_USE_CHECKANDAPPLY(pNode, pDEF, pUSE, pType, pNE) \ + do { \ + checkNodeMustBeEmpty(pNode); \ + if (!pDEF.empty()) Throw_DEF_And_USE(pNode.name()); \ + if (!FindNodeElement(pUSE, X3DElemType::pType, &pNE)) Throw_USE_NotFound(pNode.name(), pUSE); \ + mNodeElementCur->Children.push_back(pNE); /* add found object as child to current element */ \ + } while (false) + +/// \def MACRO_ATTRREAD_CHECKUSEDEF_RET +/// Compact variant for checking "USE" and "DEF". +/// \param [in] pNode - pugi xml node to read. +/// \param [out] pDEF_Var - output variable name for "DEF" value. +/// \param [out] pUSE_Var - output variable name for "USE" value. +#define MACRO_ATTRREAD_CHECKUSEDEF_RET(pNode, pDEF_Var, pUSE_Var) \ + do { \ + XmlParser::getStdStrAttribute(pNode, "def", pDEF_Var); \ + XmlParser::getStdStrAttribute(pNode, "use", pUSE_Var); \ + } while (false) + +/// \def MACRO_FACE_ADD_QUAD_FA(pCCW, pOut, pIn, pP1, pP2, pP3, pP4) +/// Add points as quad. Means that pP1..pP4 set in CCW order. +#define MACRO_FACE_ADD_QUAD_FA(pCCW, pOut, pIn, pP1, pP2, pP3, pP4) \ + do { \ + if (pCCW) { \ + pOut.push_back(pIn[pP1]); \ + pOut.push_back(pIn[pP2]); \ + pOut.push_back(pIn[pP3]); \ + pOut.push_back(pIn[pP4]); \ + } else { \ + pOut.push_back(pIn[pP4]); \ + pOut.push_back(pIn[pP3]); \ + pOut.push_back(pIn[pP2]); \ + pOut.push_back(pIn[pP1]); \ + } \ + } while (false) + +/// \def MACRO_FACE_ADD_QUAD(pCCW, pOut, pP1, pP2, pP3, pP4) +/// Add points as quad. Means that pP1..pP4 set in CCW order. +#define MACRO_FACE_ADD_QUAD(pCCW, pOut, pP1, pP2, pP3, pP4) \ + do { \ + if (pCCW) { \ + pOut.push_back(pP1); \ + pOut.push_back(pP2); \ + pOut.push_back(pP3); \ + pOut.push_back(pP4); \ + } else { \ + pOut.push_back(pP4); \ + pOut.push_back(pP3); \ + pOut.push_back(pP2); \ + pOut.push_back(pP1); \ + } \ + } while (false) + +#endif // X3DIMPORTER_MACRO_HPP_INCLUDED diff --git a/code/AssetLib/X3D/X3DImporter_Metadata.cpp b/code/AssetLib/X3D/X3DImporter_Metadata.cpp new file mode 100644 index 000000000..bb1ba9a9d --- /dev/null +++ b/code/AssetLib/X3D/X3DImporter_Metadata.cpp @@ -0,0 +1,255 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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. + +---------------------------------------------------------------------- +*/ +/// \file X3DImporter_Metadata.cpp +/// \brief Parsing data from nodes of "Metadata" set of X3D. +/// \date 2015-2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" +#include "X3DXmlHelper.h" + +namespace Assimp { + +bool X3DImporter::checkForMetadataNode(XmlNode &node) { + const std::string &name = node.name(); + if (name == "MetadataBoolean") { + readMetadataBoolean(node); + } else if (name == "MetadataDouble") { + readMetadataDouble(node); + } else if (name == "MetadataFloat") { + readMetadataFloat(node); + } else if (name == "MetadataInteger") { + readMetadataInteger(node); + } else if (name == "MetadataSet") { + readMetadataSet(node); + } else if (name == "MetadataString") { + readMetadataString(node); + } else + return false; + return true; +} + +void X3DImporter::childrenReadMetadata(XmlNode &node, X3DNodeElementBase *pParentElement, const std::string &pNodeName) { + ParseHelper_Node_Enter(pParentElement); + for (auto childNode : node.children()) { + if (!checkForMetadataNode(childNode)) skipUnsupportedNode(pNodeName, childNode); + } + ParseHelper_Node_Exit(); +} + +/// \def MACRO_METADATA_FINDCREATE(pDEF_Var, pUSE_Var, pReference, pValue, pNE, pMetaName) +/// Find element by "USE" or create new one. +/// \param [in] pNode - pugi xml node to read. +/// \param [in] pDEF_Var - variable name with "DEF" value. +/// \param [in] pUSE_Var - variable name with "USE" value. +/// \param [in] pReference - variable name with "reference" value. +/// \param [in] pValue - variable name with "value" value. +/// \param [in, out] pNE - pointer to node element. +/// \param [in] pMetaClass - Class of node. +/// \param [in] pMetaName - Name of node. +/// \param [in] pType - type of element to find. +#define MACRO_METADATA_FINDCREATE(pNode, pDEF_Var, pUSE_Var, pReference, pValue, pNE, pMetaClass, pMetaName, pType) \ + /* if "USE" defined then find already defined element. */ \ + if (!pUSE_Var.empty()) { \ + MACRO_USE_CHECKANDAPPLY(pNode, pDEF_Var, pUSE_Var, pType, pNE); \ + } else { \ + pNE = new pMetaClass(mNodeElementCur); \ + if (!pDEF_Var.empty()) pNE->ID = pDEF_Var; \ + \ + ((pMetaClass *)pNE)->Reference = pReference; \ + ((pMetaClass *)pNE)->Value = pValue; \ + /* also metadata node can contain childs */ \ + if (!isNodeEmpty(pNode)) \ + childrenReadMetadata(pNode, pNE, pMetaName); /* in that case node element will be added to child elements list of current node. */ \ + else \ + mNodeElementCur->Children.push_back(pNE); /* else - add element to child list manually */ \ + \ + NodeElement_List.push_back(pNE); /* add new element to elements list. */ \ + } /* if(!pUSE_Var.empty()) else */ \ + \ + do { \ + } while (false) + +// +void X3DImporter::readMetadataBoolean(XmlNode &node) { + std::string def, use; + std::string name, reference; + std::vector value; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getStdStrAttribute(node, "name", name); + XmlParser::getStdStrAttribute(node, "reference", reference); + X3DXmlHelper::getBooleanArrayAttribute(node, "value", value); + + MACRO_METADATA_FINDCREATE(node, def, use, reference, value, ne, X3DNodeElementMetaBoolean, "MetadataBoolean", ENET_MetaBoolean); +} + +// +void X3DImporter::readMetadataDouble(XmlNode &node) { + std::string def, use; + std::string name, reference; + std::vector value; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getStdStrAttribute(node, "name", name); + XmlParser::getStdStrAttribute(node, "reference", reference); + X3DXmlHelper::getDoubleArrayAttribute(node, "value", value); + + MACRO_METADATA_FINDCREATE(node, def, use, reference, value, ne, X3DNodeElementMetaDouble, "MetadataDouble", ENET_MetaDouble); +} + +// +void X3DImporter::readMetadataFloat(XmlNode &node) { + std::string def, use; + std::string name, reference; + std::vector value; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getStdStrAttribute(node, "name", name); + XmlParser::getStdStrAttribute(node, "reference", reference); + X3DXmlHelper::getFloatArrayAttribute(node, "value", value); + + MACRO_METADATA_FINDCREATE(node, def, use, reference, value, ne, X3DNodeElementMetaFloat, "MetadataFloat", ENET_MetaFloat); +} + +// +void X3DImporter::readMetadataInteger(XmlNode &node) { + std::string def, use; + std::string name, reference; + std::vector value; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getStdStrAttribute(node, "name", name); + XmlParser::getStdStrAttribute(node, "reference", reference); + X3DXmlHelper::getInt32ArrayAttribute(node, "value", value); + + MACRO_METADATA_FINDCREATE(node, def, use, reference, value, ne, X3DNodeElementMetaInt, "MetadataInteger", ENET_MetaInteger); +} + +// +void X3DImporter::readMetadataSet(XmlNode &node) { + std::string def, use; + std::string name, reference; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getStdStrAttribute(node, "name", name); + XmlParser::getStdStrAttribute(node, "reference", reference); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_MetaSet, ne); + } else { + ne = new X3DNodeElementMetaSet(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementMetaSet *)ne)->Reference = reference; + // also metadata node can contain childs + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "MetadataSet"); + else + mNodeElementCur->Children.push_back(ne); // made object as child to current element + + NodeElement_List.push_back(ne); // add new element to elements list. + } // if(!use.empty()) else +} + +// +void X3DImporter::readMetadataString(XmlNode &node) { + std::string def, use; + std::string name, reference; + std::vector value; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getStdStrAttribute(node, "name", name); + XmlParser::getStdStrAttribute(node, "reference", reference); + X3DXmlHelper::getStringArrayAttribute(node, "value", value); + + MACRO_METADATA_FINDCREATE(node, def, use, reference, value, ne, X3DNodeElementMetaString, "MetadataString", ENET_MetaString); +} + +}// namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/code/AssetLib/X3D/X3DImporter_Networking.cpp b/code/AssetLib/X3D/X3DImporter_Networking.cpp new file mode 100644 index 000000000..d5ef58397 --- /dev/null +++ b/code/AssetLib/X3D/X3DImporter_Networking.cpp @@ -0,0 +1,125 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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. + +---------------------------------------------------------------------- +*/ +/// \file X3DImporter_Networking.cpp +/// \brief Parsing data from nodes of "Networking" set of X3D. +/// date 2015-2016 +/// author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" +#include "X3DXmlHelper.h" + +// Header files, Assimp. +#include + +//#include + +namespace Assimp { + +//static std::regex pattern_parentDir(R"((^|/)[^/]+/../)"); +static std::string parentDir("/../"); + +// +void X3DImporter::readInline(XmlNode &node) { + std::string def, use; + bool load = true; + std::list url; + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "load", load); + X3DXmlHelper::getStringListAttribute(node, "url", url); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + X3DNodeElementBase *ne; + + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); + } else { + ParseHelper_Group_Begin(true); // create new grouping element and go deeper if node has children. + // at this place new group mode created and made current, so we can name it. + if (!def.empty()) mNodeElementCur->ID = def; + + if (load && !url.empty()) { + std::string full_path = mpIOHandler->CurrentDirectory() + url.front(); + + //full_path = std::regex_replace(full_path, pattern_parentDir, "$1"); + for (std::string::size_type pos = full_path.find(parentDir); pos != std::string::npos; pos = full_path.find(parentDir, pos)) { + if (pos > 0) { + std::string::size_type pos2 = full_path.rfind('/', pos - 1); + if (pos2 != std::string::npos) { + full_path.erase(pos2, pos - pos2 + 3); + pos = pos2; + } else { + full_path.erase(0, pos + 4); + pos = 0; + } + } else { + pos += 3; + } + } + // Attribute "url" can contain list of strings. But we need only one - first. + std::string::size_type slashPos = full_path.find_last_of("\\/"); + mpIOHandler->PushDirectory(slashPos == std::string::npos ? std::string() : full_path.substr(0, slashPos + 1)); + ParseFile(full_path, mpIOHandler); + mpIOHandler->PopDirectory(); + } + + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) childrenReadMetadata(node, mNodeElementCur, "Inline"); + + // exit from node in that place + ParseHelper_Node_Exit(); + } // if(!use.empty()) else +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/code/AssetLib/X3D/X3DImporter_Node.hpp b/code/AssetLib/X3D/X3DImporter_Node.hpp new file mode 100644 index 000000000..8079cbf11 --- /dev/null +++ b/code/AssetLib/X3D/X3DImporter_Node.hpp @@ -0,0 +1,459 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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. + +---------------------------------------------------------------------- +*/ +/// \file X3DImporter_Node.hpp +/// \brief Elements of scene graph. +/// \date 2015-2016 +/// \author smal.root@gmail.com + +#ifndef INCLUDED_AI_X3D_IMPORTER_NODE_H +#define INCLUDED_AI_X3D_IMPORTER_NODE_H + +// Header files, Assimp. +#include + +#include +#include + +enum X3DElemType { + ENET_Group, ///< Element has type "Group". + ENET_MetaBoolean, ///< Element has type "Metadata boolean". + ENET_MetaDouble, ///< Element has type "Metadata double". + ENET_MetaFloat, ///< Element has type "Metadata float". + ENET_MetaInteger, ///< Element has type "Metadata integer". + ENET_MetaSet, ///< Element has type "Metadata set". + ENET_MetaString, ///< Element has type "Metadata string". + ENET_Arc2D, ///< Element has type "Arc2D". + ENET_ArcClose2D, ///< Element has type "ArcClose2D". + ENET_Circle2D, ///< Element has type "Circle2D". + ENET_Disk2D, ///< Element has type "Disk2D". + ENET_Polyline2D, ///< Element has type "Polyline2D". + ENET_Polypoint2D, ///< Element has type "Polypoint2D". + ENET_Rectangle2D, ///< Element has type "Rectangle2D". + ENET_TriangleSet2D, ///< Element has type "TriangleSet2D". + ENET_Box, ///< Element has type "Box". + ENET_Cone, ///< Element has type "Cone". + ENET_Cylinder, ///< Element has type "Cylinder". + ENET_Sphere, ///< Element has type "Sphere". + ENET_ElevationGrid, ///< Element has type "ElevationGrid". + ENET_Extrusion, ///< Element has type "Extrusion". + ENET_Coordinate, ///< Element has type "Coordinate". + ENET_Normal, ///< Element has type "Normal". + ENET_TextureCoordinate, ///< Element has type "TextureCoordinate". + ENET_IndexedFaceSet, ///< Element has type "IndexedFaceSet". + ENET_IndexedLineSet, ///< Element has type "IndexedLineSet". + ENET_IndexedTriangleSet, ///< Element has type "IndexedTriangleSet". + ENET_IndexedTriangleFanSet, ///< Element has type "IndexedTriangleFanSet". + ENET_IndexedTriangleStripSet, ///< Element has type "IndexedTriangleStripSet". + ENET_LineSet, ///< Element has type "LineSet". + ENET_PointSet, ///< Element has type "PointSet". + ENET_TriangleSet, ///< Element has type "TriangleSet". + ENET_TriangleFanSet, ///< Element has type "TriangleFanSet". + ENET_TriangleStripSet, ///< Element has type "TriangleStripSet". + ENET_Color, ///< Element has type "Color". + ENET_ColorRGBA, ///< Element has type "ColorRGBA". + ENET_Shape, ///< Element has type "Shape". + ENET_Appearance, ///< Element has type "Appearance". + ENET_Material, ///< Element has type "Material". + ENET_ImageTexture, ///< Element has type "ImageTexture". + ENET_TextureTransform, ///< Element has type "TextureTransform". + ENET_DirectionalLight, ///< Element has type "DirectionalLight". + ENET_PointLight, ///< Element has type "PointLight". + ENET_SpotLight, ///< Element has type "SpotLight". + + ENET_Invalid ///< Element has invalid type and possible contain invalid data. +}; + +struct X3DNodeElementBase { + X3DNodeElementBase *Parent; + std::string ID; + std::list Children; + X3DElemType Type; + +protected: + X3DNodeElementBase(X3DElemType type, X3DNodeElementBase *pParent) : + Type(type), Parent(pParent) { + // empty + } +}; + +/// This struct hold value. +struct X3DNodeElementColor : X3DNodeElementBase { + std::list Value; ///< Stored value. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + X3DNodeElementColor(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_Color, pParent) {} + +}; // struct X3DNodeElementColor + +/// This struct hold value. +struct X3DNodeElementColorRGBA : X3DNodeElementBase { + std::list Value; ///< Stored value. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + X3DNodeElementColorRGBA(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_ColorRGBA, pParent) {} + +}; // struct X3DNodeElementColorRGBA + +/// This struct hold value. +struct X3DNodeElementCoordinate : public X3DNodeElementBase { + std::list Value; ///< Stored value. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + X3DNodeElementCoordinate(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_Coordinate, pParent) {} + +}; // struct X3DNodeElementCoordinate + +/// This struct hold value. +struct X3DNodeElementNormal : X3DNodeElementBase { + std::list Value; ///< Stored value. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + X3DNodeElementNormal(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_Normal, pParent) {} + +}; // struct X3DNodeElementNormal + +/// This struct hold value. +struct X3DNodeElementTextureCoordinate : X3DNodeElementBase { + std::list Value; ///< Stored value. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + X3DNodeElementTextureCoordinate(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_TextureCoordinate, pParent) {} + +}; // struct X3DNodeElementTextureCoordinate + +/// Two-dimensional figure. +struct X3DNodeElementGeometry2D : X3DNodeElementBase { + std::list Vertices; ///< Vertices list. + size_t NumIndices; ///< Number of indices in one face. + bool Solid; ///< Flag: if true then render must use back-face culling, else render must draw both sides of object. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pType - type of geometry object. + X3DNodeElementGeometry2D(X3DElemType pType, X3DNodeElementBase *pParent) : + X3DNodeElementBase(pType, pParent), Solid(true) {} + +}; // class X3DNodeElementGeometry2D + +/// Three-dimensional body. +struct X3DNodeElementGeometry3D : X3DNodeElementBase { + std::list Vertices; ///< Vertices list. + size_t NumIndices; ///< Number of indices in one face. + bool Solid; ///< Flag: if true then render must use back-face culling, else render must draw both sides of object. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pType - type of geometry object. + X3DNodeElementGeometry3D(X3DElemType pType, X3DNodeElementBase *pParent) : + X3DNodeElementBase(pType, pParent), Vertices(), NumIndices(0), Solid(true) { + // empty + } +}; // class X3DNodeElementGeometry3D + +/// Uniform rectangular grid of varying height. +struct X3DNodeElementElevationGrid : X3DNodeElementGeometry3D { + bool NormalPerVertex; ///< If true then normals are defined for every vertex, else for every face(line). + bool ColorPerVertex; ///< If true then colors are defined for every vertex, else for every face(line). + /// If the angle between the geometric normals of two adjacent faces is less than the crease angle, normals shall be calculated so that the faces are + /// shaded smoothly across the edge; otherwise, normals shall be calculated so that a lighting discontinuity across the edge is produced. + float CreaseAngle; + std::vector CoordIdx; ///< Coordinates list by faces. In X3D format: "-1" - delimiter for faces. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pType - type of geometry object. + X3DNodeElementElevationGrid(X3DElemType pType, X3DNodeElementBase *pParent) : + X3DNodeElementGeometry3D(pType, pParent) {} +}; // class X3DNodeElementIndexedSet + +/// Shape with indexed vertices. +struct X3DNodeElementIndexedSet : public X3DNodeElementGeometry3D { + /// The ccw field defines the ordering of the vertex coordinates of the geometry with respect to user-given or automatically generated normal vectors + /// used in the lighting model equations. If ccw is TRUE, the normals shall follow the right hand rule; the orientation of each normal with respect to + /// the vertices (taken in order) shall be such that the vertices appear to be oriented in a counterclockwise order when the vertices are viewed (in the + /// local coordinate system of the Shape) from the opposite direction as the normal. If ccw is FALSE, the normals shall be oriented in the opposite + /// direction. If normals are not generated but are supplied using a Normal node, and the orientation of the normals does not match the setting of the + /// ccw field, results are undefined. + bool CCW; + std::vector ColorIndex; ///< Field to specify the polygonal faces by indexing into the or . + bool ColorPerVertex; ///< If true then colors are defined for every vertex, else for every face(line). + /// The convex field indicates whether all polygons in the shape are convex (TRUE). A polygon is convex if it is planar, does not intersect itself, + /// and all of the interior angles at its vertices are less than 180 degrees. Non planar and self intersecting polygons may produce undefined results + /// even if the convex field is FALSE. + bool Convex; + std::vector CoordIndex; ///< Field to specify the polygonal faces by indexing into the . + /// If the angle between the geometric normals of two adjacent faces is less than the crease angle, normals shall be calculated so that the faces are + /// shaded smoothly across the edge; otherwise, normals shall be calculated so that a lighting discontinuity across the edge is produced. + float CreaseAngle; + std::vector NormalIndex; ///< Field to specify the polygonal faces by indexing into the . + bool NormalPerVertex; ///< If true then normals are defined for every vertex, else for every face(line). + std::vector TexCoordIndex; ///< Field to specify the polygonal faces by indexing into the . + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pType - type of geometry object. + X3DNodeElementIndexedSet(X3DElemType pType, X3DNodeElementBase *pParent) : + X3DNodeElementGeometry3D(pType, pParent) {} +}; // class X3DNodeElementIndexedSet + +/// Shape with set of vertices. +struct X3DNodeElementSet : X3DNodeElementGeometry3D { + /// The ccw field defines the ordering of the vertex coordinates of the geometry with respect to user-given or automatically generated normal vectors + /// used in the lighting model equations. If ccw is TRUE, the normals shall follow the right hand rule; the orientation of each normal with respect to + /// the vertices (taken in order) shall be such that the vertices appear to be oriented in a counterclockwise order when the vertices are viewed (in the + /// local coordinate system of the Shape) from the opposite direction as the normal. If ccw is FALSE, the normals shall be oriented in the opposite + /// direction. If normals are not generated but are supplied using a Normal node, and the orientation of the normals does not match the setting of the + /// ccw field, results are undefined. + bool CCW; + bool ColorPerVertex; ///< If true then colors are defined for every vertex, else for every face(line). + bool NormalPerVertex; ///< If true then normals are defined for every vertex, else for every face(line). + std::vector CoordIndex; ///< Field to specify the polygonal faces by indexing into the . + std::vector NormalIndex; ///< Field to specify the polygonal faces by indexing into the . + std::vector TexCoordIndex; ///< Field to specify the polygonal faces by indexing into the . + std::vector VertexCount; ///< Field describes how many vertices are to be used in each polyline(polygon) from the field. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pType - type of geometry object. + X3DNodeElementSet(X3DElemType type, X3DNodeElementBase *pParent) : + X3DNodeElementGeometry3D(type, pParent) {} + +}; // class X3DNodeElementSet + +/// This struct hold value. +struct X3DNodeElementShape : X3DNodeElementBase { + + /// Constructor + /// \param [in] pParent - pointer to parent node. + X3DNodeElementShape(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_Shape, pParent) {} +}; // struct X3DNodeElementShape + +/// This struct hold value. +struct X3DNodeElementAppearance : public X3DNodeElementBase { + + /// Constructor + /// \param [in] pParent - pointer to parent node. + X3DNodeElementAppearance(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_Appearance, pParent) {} + +}; // struct X3DNodeElementAppearance + +struct X3DNodeElementMaterial : public X3DNodeElementBase { + float AmbientIntensity; ///< Specifies how much ambient light from light sources this surface shall reflect. + aiColor3D DiffuseColor; ///< Reflects all X3D light sources depending on the angle of the surface with respect to the light source. + aiColor3D EmissiveColor; ///< Models "glowing" objects. This can be useful for displaying pre-lit models. + float Shininess; ///< Lower shininess values produce soft glows, while higher values result in sharper, smaller highlights. + aiColor3D SpecularColor; ///< The specularColor and shininess fields determine the specular highlights. + float Transparency; ///< Specifies how "clear" an object is, with 1.0 being completely transparent, and 0.0 completely opaque. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pType - type of geometry object. + X3DNodeElementMaterial(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_Material, pParent), + AmbientIntensity(0.0f), + DiffuseColor(), + EmissiveColor(), + Shininess(0.0f), + SpecularColor(), + Transparency(1.0f) { + // empty + } +}; // class X3DNodeElementMaterial + +/// This struct hold value. +struct X3DNodeElementImageTexture : X3DNodeElementBase { + /// RepeatS and RepeatT, that specify how the texture wraps in the S and T directions. If repeatS is TRUE (the default), the texture map is repeated + /// outside the [0.0, 1.0] texture coordinate range in the S direction so that it fills the shape. If repeatS is FALSE, the texture coordinates are + /// clamped in the S direction to lie within the [0.0, 1.0] range. The repeatT field is analogous to the repeatS field. + bool RepeatS; + bool RepeatT; ///< See \ref RepeatS. + std::string URL; ///< URL of the texture. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + X3DNodeElementImageTexture(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_ImageTexture, pParent) {} + +}; // struct X3DNodeElementImageTexture + +/// This struct hold value. +struct X3DNodeElementTextureTransform : X3DNodeElementBase { + aiVector2D Center; ///< Specifies a translation offset in texture coordinate space about which the rotation and scale fields are applied. + float Rotation; ///< Specifies a rotation in angle base units of the texture coordinates about the center point after the scale has been applied. + aiVector2D Scale; ///< Specifies a scaling factor in S and T of the texture coordinates about the center point. + aiVector2D Translation; ///< Specifies a translation of the texture coordinates. + + /// Constructor + /// \param [in] pParent - pointer to parent node. + X3DNodeElementTextureTransform(X3DNodeElementBase *pParent) : + X3DNodeElementBase(X3DElemType::ENET_TextureTransform, pParent) {} + +}; // struct X3DNodeElementTextureTransform + +struct X3DNodeElementGroup : X3DNodeElementBase { + aiMatrix4x4 Transformation; ///< Transformation matrix. + + /// As you know node elements can use already defined node elements when attribute "USE" is defined. + /// Standard search when looking for an element in the whole scene graph, existing at this moment. + /// If a node is marked as static, the children(or lower) can not search for elements in the nodes upper then static. + bool Static; + + bool UseChoice; ///< Flag: if true then use number from \ref Choice to choose what the child will be kept. + int32_t Choice; ///< Number of the child which will be kept. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + /// \param [in] pStatic - static node flag. + X3DNodeElementGroup(X3DNodeElementBase *pParent, const bool pStatic = false) : + X3DNodeElementBase(X3DElemType::ENET_Group, pParent), Static(pStatic), UseChoice(false) {} +}; + +struct X3DNodeElementMeta : X3DNodeElementBase { + std::string Name; ///< Name of metadata object. + std::string Reference; + + virtual ~X3DNodeElementMeta() { + // empty + } + +protected: + X3DNodeElementMeta(X3DElemType type, X3DNodeElementBase *parent) : + X3DNodeElementBase(type, parent) { + // empty + } +}; + +struct X3DNodeElementMetaBoolean : X3DNodeElementMeta { + std::vector Value; ///< Stored value. + + explicit X3DNodeElementMetaBoolean(X3DNodeElementBase *pParent) : + X3DNodeElementMeta(X3DElemType::ENET_MetaBoolean, pParent) { + // empty + } +}; + +struct X3DNodeElementMetaDouble : X3DNodeElementMeta { + std::vector Value; ///< Stored value. + + explicit X3DNodeElementMetaDouble(X3DNodeElementBase *pParent) : + X3DNodeElementMeta(X3DElemType::ENET_MetaDouble, pParent) { + // empty + } +}; + +struct X3DNodeElementMetaFloat : public X3DNodeElementMeta { + std::vector Value; ///< Stored value. + + explicit X3DNodeElementMetaFloat(X3DNodeElementBase *pParent) : + X3DNodeElementMeta(X3DElemType::ENET_MetaFloat, pParent) { + // empty + } +}; + +struct X3DNodeElementMetaInt : public X3DNodeElementMeta { + std::vector Value; ///< Stored value. + + explicit X3DNodeElementMetaInt(X3DNodeElementBase *pParent) : + X3DNodeElementMeta(X3DElemType::ENET_MetaInteger, pParent) { + // empty + } +}; + +struct X3DNodeElementMetaSet : public X3DNodeElementMeta { + std::list Value; ///< Stored value. + + explicit X3DNodeElementMetaSet(X3DNodeElementBase *pParent) : + X3DNodeElementMeta(X3DElemType::ENET_MetaSet, pParent) { + // empty + } +}; + +struct X3DNodeElementMetaString : X3DNodeElementMeta { + std::vector Value; ///< Stored value. + + explicit X3DNodeElementMetaString(X3DNodeElementBase *pParent) : + X3DNodeElementMeta(X3DElemType::ENET_MetaString, pParent) { + // empty + } +}; + +/// \struct X3DNodeElementLight +/// This struct hold value. +struct X3DNodeElementLight : X3DNodeElementBase { + float AmbientIntensity; ///< Specifies the intensity of the ambient emission from the light. + aiColor3D Color; ///< specifies the spectral colour properties of both the direct and ambient light emission as an RGB value. + aiVector3D Direction; ///< Specifies the direction vector of the illumination emanating from the light source in the local coordinate system. + /// \var Global + /// Field that determines whether the light is global or scoped. Global lights illuminate all objects that fall within their volume of lighting influence. + /// Scoped lights only illuminate objects that are in the same transformation hierarchy as the light. + bool Global; + float Intensity; ///< Specifies the brightness of the direct emission from the light. + /// \var Attenuation + /// PointLight node's illumination falls off with distance as specified by three attenuation coefficients. The attenuation factor + /// is: "1 / max(attenuation[0] + attenuation[1] * r + attenuation[2] * r2, 1)", where r is the distance from the light to the surface being illuminated. + aiVector3D Attenuation; + aiVector3D Location; ///< Specifies a translation offset of the centre point of the light source from the light's local coordinate system origin. + float Radius; ///< Specifies the radial extent of the solid angle and the maximum distance from location that may be illuminated by the light source. + float BeamWidth; ///< Specifies an inner solid angle in which the light source emits light at uniform full intensity. + float CutOffAngle; ///< The light source's emission intensity drops off from the inner solid angle (beamWidth) to the outer solid angle (cutOffAngle). + + /// Constructor + /// \param [in] pParent - pointer to parent node. + /// \param [in] pLightType - type of the light source. + X3DNodeElementLight(X3DElemType pLightType, X3DNodeElementBase *pParent) : + X3DNodeElementBase(pLightType, pParent) {} + +}; // struct X3DNodeElementLight + +#endif // INCLUDED_AI_X3D_IMPORTER_NODE_H diff --git a/code/AssetLib/X3D/X3DImporter_Postprocess.cpp b/code/AssetLib/X3D/X3DImporter_Postprocess.cpp new file mode 100644 index 000000000..24e03ca16 --- /dev/null +++ b/code/AssetLib/X3D/X3DImporter_Postprocess.cpp @@ -0,0 +1,731 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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. + +---------------------------------------------------------------------- +*/ +/// \file X3DImporter_Postprocess.cpp +/// \brief Convert built scenegraph and objects to Assimp scenegraph. +/// \date 2015-2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DGeoHelper.h" +#include "X3DImporter.hpp" + +// Header files, Assimp. +#include +#include +#include + +// Header files, stdlib. +#include +#include +#include + +namespace Assimp { + +aiMatrix4x4 X3DImporter::PostprocessHelper_Matrix_GlobalToCurrent() const { + X3DNodeElementBase *cur_node; + std::list matr; + aiMatrix4x4 out_matr; + + // starting walk from current element to root + cur_node = mNodeElementCur; + if (cur_node != nullptr) { + do { + // if cur_node is group then store group transformation matrix in list. + if (cur_node->Type == X3DElemType::ENET_Group) matr.push_back(((X3DNodeElementGroup *)cur_node)->Transformation); + + cur_node = cur_node->Parent; + } while (cur_node != nullptr); + } + + // multiplicate all matrices in reverse order + for (std::list::reverse_iterator rit = matr.rbegin(); rit != matr.rend(); ++rit) + out_matr = out_matr * (*rit); + + return out_matr; +} + +void X3DImporter::PostprocessHelper_CollectMetadata(const X3DNodeElementBase &pNodeElement, std::list &pList) const { + // walk through childs and find for metadata. + for (std::list::const_iterator el_it = pNodeElement.Children.begin(); el_it != pNodeElement.Children.end(); ++el_it) { + if (((*el_it)->Type == X3DElemType::ENET_MetaBoolean) || ((*el_it)->Type == X3DElemType::ENET_MetaDouble) || + ((*el_it)->Type == X3DElemType::ENET_MetaFloat) || ((*el_it)->Type == X3DElemType::ENET_MetaInteger) || + ((*el_it)->Type == X3DElemType::ENET_MetaString)) { + pList.push_back(*el_it); + } else if ((*el_it)->Type == X3DElemType::ENET_MetaSet) { + PostprocessHelper_CollectMetadata(**el_it, pList); + } + } // for(std::list::const_iterator el_it = pNodeElement.Children.begin(); el_it != pNodeElement.Children.end(); el_it++) +} + +bool X3DImporter::PostprocessHelper_ElementIsMetadata(const X3DElemType pType) const { + if ((pType == X3DElemType::ENET_MetaBoolean) || (pType == X3DElemType::ENET_MetaDouble) || + (pType == X3DElemType::ENET_MetaFloat) || (pType == X3DElemType::ENET_MetaInteger) || + (pType == X3DElemType::ENET_MetaString) || (pType == X3DElemType::ENET_MetaSet)) { + return true; + } else { + return false; + } +} + +bool X3DImporter::PostprocessHelper_ElementIsMesh(const X3DElemType pType) const { + if ((pType == X3DElemType::ENET_Arc2D) || (pType == X3DElemType::ENET_ArcClose2D) || + (pType == X3DElemType::ENET_Box) || (pType == X3DElemType::ENET_Circle2D) || + (pType == X3DElemType::ENET_Cone) || (pType == X3DElemType::ENET_Cylinder) || + (pType == X3DElemType::ENET_Disk2D) || (pType == X3DElemType::ENET_ElevationGrid) || + (pType == X3DElemType::ENET_Extrusion) || (pType == X3DElemType::ENET_IndexedFaceSet) || + (pType == X3DElemType::ENET_IndexedLineSet) || (pType == X3DElemType::ENET_IndexedTriangleFanSet) || + (pType == X3DElemType::ENET_IndexedTriangleSet) || (pType == X3DElemType::ENET_IndexedTriangleStripSet) || + (pType == X3DElemType::ENET_PointSet) || (pType == X3DElemType::ENET_LineSet) || + (pType == X3DElemType::ENET_Polyline2D) || (pType == X3DElemType::ENET_Polypoint2D) || + (pType == X3DElemType::ENET_Rectangle2D) || (pType == X3DElemType::ENET_Sphere) || + (pType == X3DElemType::ENET_TriangleFanSet) || (pType == X3DElemType::ENET_TriangleSet) || + (pType == X3DElemType::ENET_TriangleSet2D) || (pType == X3DElemType::ENET_TriangleStripSet)) { + return true; + } else { + return false; + } +} + +void X3DImporter::Postprocess_BuildLight(const X3DNodeElementBase &pNodeElement, std::list &pSceneLightList) const { + const X3DNodeElementLight &ne = *((X3DNodeElementLight *)&pNodeElement); + aiMatrix4x4 transform_matr = PostprocessHelper_Matrix_GlobalToCurrent(); + aiLight *new_light = new aiLight; + + new_light->mName = ne.ID; + new_light->mColorAmbient = ne.Color * ne.AmbientIntensity; + new_light->mColorDiffuse = ne.Color * ne.Intensity; + new_light->mColorSpecular = ne.Color * ne.Intensity; + switch (pNodeElement.Type) { + case X3DElemType::ENET_DirectionalLight: + new_light->mType = aiLightSource_DIRECTIONAL; + new_light->mDirection = ne.Direction, new_light->mDirection *= transform_matr; + + break; + case X3DElemType::ENET_PointLight: + new_light->mType = aiLightSource_POINT; + new_light->mPosition = ne.Location, new_light->mPosition *= transform_matr; + new_light->mAttenuationConstant = ne.Attenuation.x; + new_light->mAttenuationLinear = ne.Attenuation.y; + new_light->mAttenuationQuadratic = ne.Attenuation.z; + + break; + case X3DElemType::ENET_SpotLight: + new_light->mType = aiLightSource_SPOT; + new_light->mPosition = ne.Location, new_light->mPosition *= transform_matr; + new_light->mDirection = ne.Direction, new_light->mDirection *= transform_matr; + new_light->mAttenuationConstant = ne.Attenuation.x; + new_light->mAttenuationLinear = ne.Attenuation.y; + new_light->mAttenuationQuadratic = ne.Attenuation.z; + new_light->mAngleInnerCone = ne.BeamWidth; + new_light->mAngleOuterCone = ne.CutOffAngle; + + break; + default: + throw DeadlyImportError("Postprocess_BuildLight. Unknown type of light: " + ai_to_string(pNodeElement.Type) + "."); + } + + pSceneLightList.push_back(new_light); +} + +void X3DImporter::Postprocess_BuildMaterial(const X3DNodeElementBase &pNodeElement, aiMaterial **pMaterial) const { + // check argument + if (pMaterial == nullptr) throw DeadlyImportError("Postprocess_BuildMaterial. pMaterial is nullptr."); + if (*pMaterial != nullptr) throw DeadlyImportError("Postprocess_BuildMaterial. *pMaterial must be nullptr."); + + *pMaterial = new aiMaterial; + aiMaterial &taimat = **pMaterial; // creating alias for convenience. + + // at this point pNodeElement point to node. Walk through childs and add all stored data. + for (std::list::const_iterator el_it = pNodeElement.Children.begin(); el_it != pNodeElement.Children.end(); ++el_it) { + if ((*el_it)->Type == X3DElemType::ENET_Material) { + aiColor3D tcol3; + float tvalf; + X3DNodeElementMaterial &tnemat = *((X3DNodeElementMaterial *)*el_it); + + tcol3.r = tnemat.AmbientIntensity, tcol3.g = tnemat.AmbientIntensity, tcol3.b = tnemat.AmbientIntensity; + taimat.AddProperty(&tcol3, 1, AI_MATKEY_COLOR_AMBIENT); + taimat.AddProperty(&tnemat.DiffuseColor, 1, AI_MATKEY_COLOR_DIFFUSE); + taimat.AddProperty(&tnemat.EmissiveColor, 1, AI_MATKEY_COLOR_EMISSIVE); + taimat.AddProperty(&tnemat.SpecularColor, 1, AI_MATKEY_COLOR_SPECULAR); + tvalf = 1; + taimat.AddProperty(&tvalf, 1, AI_MATKEY_SHININESS_STRENGTH); + taimat.AddProperty(&tnemat.Shininess, 1, AI_MATKEY_SHININESS); + tvalf = 1.0f - tnemat.Transparency; + taimat.AddProperty(&tvalf, 1, AI_MATKEY_OPACITY); + } // if((*el_it)->Type == X3DElemType::ENET_Material) + else if ((*el_it)->Type == X3DElemType::ENET_ImageTexture) { + X3DNodeElementImageTexture &tnetex = *((X3DNodeElementImageTexture *)*el_it); + aiString url_str(tnetex.URL.c_str()); + int mode = aiTextureOp_Multiply; + + taimat.AddProperty(&url_str, AI_MATKEY_TEXTURE_DIFFUSE(0)); + taimat.AddProperty(&tnetex.RepeatS, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0)); + taimat.AddProperty(&tnetex.RepeatT, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0)); + taimat.AddProperty(&mode, 1, AI_MATKEY_TEXOP_DIFFUSE(0)); + } // else if((*el_it)->Type == X3DElemType::ENET_ImageTexture) + else if ((*el_it)->Type == X3DElemType::ENET_TextureTransform) { + aiUVTransform trans; + X3DNodeElementTextureTransform &tnetextr = *((X3DNodeElementTextureTransform *)*el_it); + + trans.mTranslation = tnetextr.Translation - tnetextr.Center; + trans.mScaling = tnetextr.Scale; + trans.mRotation = tnetextr.Rotation; + taimat.AddProperty(&trans, 1, AI_MATKEY_UVTRANSFORM_DIFFUSE(0)); + } // else if((*el_it)->Type == X3DElemType::ENET_TextureTransform) + } // for(std::list::const_iterator el_it = pNodeElement.Children.begin(); el_it != pNodeElement.Children.end(); el_it++) +} + +void X3DImporter::Postprocess_BuildMesh(const X3DNodeElementBase &pNodeElement, aiMesh **pMesh) const { + // check argument + if (pMesh == nullptr) throw DeadlyImportError("Postprocess_BuildMesh. pMesh is nullptr."); + if (*pMesh != nullptr) throw DeadlyImportError("Postprocess_BuildMesh. *pMesh must be nullptr."); + + /************************************************************************************************************************************/ + /************************************************************ Geometry2D ************************************************************/ + /************************************************************************************************************************************/ + if ((pNodeElement.Type == X3DElemType::ENET_Arc2D) || (pNodeElement.Type == X3DElemType::ENET_ArcClose2D) || + (pNodeElement.Type == X3DElemType::ENET_Circle2D) || (pNodeElement.Type == X3DElemType::ENET_Disk2D) || + (pNodeElement.Type == X3DElemType::ENET_Polyline2D) || (pNodeElement.Type == X3DElemType::ENET_Polypoint2D) || + (pNodeElement.Type == X3DElemType::ENET_Rectangle2D) || (pNodeElement.Type == X3DElemType::ENET_TriangleSet2D)) { + X3DNodeElementGeometry2D &tnemesh = *((X3DNodeElementGeometry2D *)&pNodeElement); // create alias for convenience + std::vector tarr; + + tarr.reserve(tnemesh.Vertices.size()); + for (std::list::iterator it = tnemesh.Vertices.begin(); it != tnemesh.Vertices.end(); ++it) + tarr.push_back(*it); + *pMesh = StandardShapes::MakeMesh(tarr, static_cast(tnemesh.NumIndices)); // create mesh from vertices using Assimp help. + + return; // mesh is build, nothing to do anymore. + } + /************************************************************************************************************************************/ + /************************************************************ Geometry3D ************************************************************/ + /************************************************************************************************************************************/ + // + // Predefined figures + // + if ((pNodeElement.Type == X3DElemType::ENET_Box) || (pNodeElement.Type == X3DElemType::ENET_Cone) || + (pNodeElement.Type == X3DElemType::ENET_Cylinder) || (pNodeElement.Type == X3DElemType::ENET_Sphere)) { + X3DNodeElementGeometry3D &tnemesh = *((X3DNodeElementGeometry3D *)&pNodeElement); // create alias for convenience + std::vector tarr; + + tarr.reserve(tnemesh.Vertices.size()); + for (std::list::iterator it = tnemesh.Vertices.begin(); it != tnemesh.Vertices.end(); ++it) + tarr.push_back(*it); + + *pMesh = StandardShapes::MakeMesh(tarr, static_cast(tnemesh.NumIndices)); // create mesh from vertices using Assimp help. + + return; // mesh is build, nothing to do anymore. + } + // + // Parametric figures + // + if (pNodeElement.Type == X3DElemType::ENET_ElevationGrid) { + X3DNodeElementElevationGrid &tnemesh = *((X3DNodeElementElevationGrid *)&pNodeElement); // create alias for convenience + + // at first create mesh from existing vertices. + *pMesh = X3DGeoHelper::make_mesh(tnemesh.CoordIdx, tnemesh.Vertices); + // copy additional information from children + for (std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Color) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColor *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_ColorRGBA) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColorRGBA *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_Normal) + X3DGeoHelper::add_normal(**pMesh, ((X3DNodeElementNormal *)*ch_it)->Value, tnemesh.NormalPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_TextureCoordinate) + X3DGeoHelper::add_tex_coord(**pMesh, ((X3DNodeElementTextureCoordinate *)*ch_it)->Value); + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of ElevationGrid: " + ai_to_string((*ch_it)->Type) + "."); + } // for(std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == X3DElemType::ENET_ElevationGrid) + // + // Indexed primitives sets + // + if (pNodeElement.Type == X3DElemType::ENET_IndexedFaceSet) { + X3DNodeElementIndexedSet &tnemesh = *((X3DNodeElementIndexedSet *)&pNodeElement); // create alias for convenience + + // at first search for node and create mesh. + for (std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + *pMesh = X3DGeoHelper::make_mesh(tnemesh.CoordIndex, ((X3DNodeElementCoordinate *)*ch_it)->Value); + } + } + + // copy additional information from children + for (std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Color) + X3DGeoHelper::add_color(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((X3DNodeElementColor *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_ColorRGBA) + X3DGeoHelper::add_color(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((X3DNodeElementColorRGBA *)*ch_it)->Value, + tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + } // skip because already read when mesh created. + else if ((*ch_it)->Type == X3DElemType::ENET_Normal) + X3DGeoHelper::add_normal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((X3DNodeElementNormal *)*ch_it)->Value, + tnemesh.NormalPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_TextureCoordinate) + X3DGeoHelper::add_tex_coord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((X3DNodeElementTextureCoordinate *)*ch_it)->Value); + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedFaceSet: " + ai_to_string((*ch_it)->Type) + "."); + } // for(std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == X3DElemType::ENET_IndexedFaceSet) + + if (pNodeElement.Type == X3DElemType::ENET_IndexedLineSet) { + X3DNodeElementIndexedSet &tnemesh = *((X3DNodeElementIndexedSet *)&pNodeElement); // create alias for convenience + + // at first search for node and create mesh. + for (std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + *pMesh = X3DGeoHelper::make_mesh(tnemesh.CoordIndex, ((X3DNodeElementCoordinate *)*ch_it)->Value); + } + } + + // copy additional information from children + for (std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + ai_assert(*pMesh); + if ((*ch_it)->Type == X3DElemType::ENET_Color) + X3DGeoHelper::add_color(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((X3DNodeElementColor *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_ColorRGBA) + X3DGeoHelper::add_color(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((X3DNodeElementColorRGBA *)*ch_it)->Value, + tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + } // skip because already read when mesh created. + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedLineSet: " + ai_to_string((*ch_it)->Type) + "."); + } // for(std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == X3DElemType::ENET_IndexedLineSet) + + if ((pNodeElement.Type == X3DElemType::ENET_IndexedTriangleSet) || + (pNodeElement.Type == X3DElemType::ENET_IndexedTriangleFanSet) || + (pNodeElement.Type == X3DElemType::ENET_IndexedTriangleStripSet)) { + X3DNodeElementIndexedSet &tnemesh = *((X3DNodeElementIndexedSet *)&pNodeElement); // create alias for convenience + + // at first search for node and create mesh. + for (std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + *pMesh = X3DGeoHelper::make_mesh(tnemesh.CoordIndex, ((X3DNodeElementCoordinate *)*ch_it)->Value); + } + } + + // copy additional information from children + for (std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + ai_assert(*pMesh); + if ((*ch_it)->Type == X3DElemType::ENET_Color) + X3DGeoHelper::add_color(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((X3DNodeElementColor *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_ColorRGBA) + X3DGeoHelper::add_color(**pMesh, tnemesh.CoordIndex, tnemesh.ColorIndex, ((X3DNodeElementColorRGBA *)*ch_it)->Value, + tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + } // skip because already read when mesh created. + else if ((*ch_it)->Type == X3DElemType::ENET_Normal) + X3DGeoHelper::add_normal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((X3DNodeElementNormal *)*ch_it)->Value, + tnemesh.NormalPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_TextureCoordinate) + X3DGeoHelper::add_tex_coord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((X3DNodeElementTextureCoordinate *)*ch_it)->Value); + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of IndexedTriangleSet or IndexedTriangleFanSet, or \ + IndexedTriangleStripSet: " + + ai_to_string((*ch_it)->Type) + "."); + } // for(std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if((pNodeElement.Type == X3DElemType::ENET_IndexedTriangleFanSet) || (pNodeElement.Type == X3DElemType::ENET_IndexedTriangleStripSet)) + + if (pNodeElement.Type == X3DElemType::ENET_Extrusion) { + X3DNodeElementIndexedSet &tnemesh = *((X3DNodeElementIndexedSet *)&pNodeElement); // create alias for convenience + + *pMesh = X3DGeoHelper::make_mesh(tnemesh.CoordIndex, tnemesh.Vertices); + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == X3DElemType::ENET_Extrusion) + + // + // Primitives sets + // + if (pNodeElement.Type == X3DElemType::ENET_PointSet) { + X3DNodeElementSet &tnemesh = *((X3DNodeElementSet *)&pNodeElement); // create alias for convenience + + // at first search for node and create mesh. + for (std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + std::vector vec_copy; + + vec_copy.reserve(((X3DNodeElementCoordinate *)*ch_it)->Value.size()); + for (std::list::const_iterator it = ((X3DNodeElementCoordinate *)*ch_it)->Value.begin(); + it != ((X3DNodeElementCoordinate *)*ch_it)->Value.end(); ++it) { + vec_copy.push_back(*it); + } + + *pMesh = StandardShapes::MakeMesh(vec_copy, 1); + } + } + + // copy additional information from children + for (std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + ai_assert(*pMesh); + if ((*ch_it)->Type == X3DElemType::ENET_Color) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColor *)*ch_it)->Value, true); + else if ((*ch_it)->Type == X3DElemType::ENET_ColorRGBA) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColorRGBA *)*ch_it)->Value, true); + else if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + } // skip because already read when mesh created. + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of PointSet: " + ai_to_string((*ch_it)->Type) + "."); + } // for(std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == X3DElemType::ENET_PointSet) + + if (pNodeElement.Type == X3DElemType::ENET_LineSet) { + X3DNodeElementSet &tnemesh = *((X3DNodeElementSet *)&pNodeElement); // create alias for convenience + + // at first search for node and create mesh. + for (std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + *pMesh = X3DGeoHelper::make_mesh(tnemesh.CoordIndex, ((X3DNodeElementCoordinate *)*ch_it)->Value); + } + } + + // copy additional information from children + for (std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + ai_assert(*pMesh); + if ((*ch_it)->Type == X3DElemType::ENET_Color) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColor *)*ch_it)->Value, true); + else if ((*ch_it)->Type == X3DElemType::ENET_ColorRGBA) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColorRGBA *)*ch_it)->Value, true); + else if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + } // skip because already read when mesh created. + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of LineSet: " + ai_to_string((*ch_it)->Type) + "."); + } // for(std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == X3DElemType::ENET_LineSet) + + if (pNodeElement.Type == X3DElemType::ENET_TriangleFanSet) { + X3DNodeElementSet &tnemesh = *((X3DNodeElementSet *)&pNodeElement); // create alias for convenience + + // at first search for node and create mesh. + for (std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + *pMesh = X3DGeoHelper::make_mesh(tnemesh.CoordIndex, ((X3DNodeElementCoordinate *)*ch_it)->Value); + } + } + + // copy additional information from children + for (std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if (nullptr == *pMesh) { + break; + } + if ((*ch_it)->Type == X3DElemType::ENET_Color) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColor *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_ColorRGBA) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColorRGBA *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + } // skip because already read when mesh created. + else if ((*ch_it)->Type == X3DElemType::ENET_Normal) + X3DGeoHelper::add_normal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((X3DNodeElementNormal *)*ch_it)->Value, + tnemesh.NormalPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_TextureCoordinate) + X3DGeoHelper::add_tex_coord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((X3DNodeElementTextureCoordinate *)*ch_it)->Value); + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TrianlgeFanSet: " + ai_to_string((*ch_it)->Type) + "."); + } // for(std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == X3DElemType::ENET_TriangleFanSet) + + if (pNodeElement.Type == X3DElemType::ENET_TriangleSet) { + X3DNodeElementSet &tnemesh = *((X3DNodeElementSet *)&pNodeElement); // create alias for convenience + + // at first search for node and create mesh. + for (std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + std::vector vec_copy; + + vec_copy.reserve(((X3DNodeElementCoordinate *)*ch_it)->Value.size()); + for (std::list::const_iterator it = ((X3DNodeElementCoordinate *)*ch_it)->Value.begin(); + it != ((X3DNodeElementCoordinate *)*ch_it)->Value.end(); ++it) { + vec_copy.push_back(*it); + } + + *pMesh = StandardShapes::MakeMesh(vec_copy, 3); + } + } + + // copy additional information from children + for (std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + ai_assert(*pMesh); + if ((*ch_it)->Type == X3DElemType::ENET_Color) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColor *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_ColorRGBA) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColorRGBA *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + } // skip because already read when mesh created. + else if ((*ch_it)->Type == X3DElemType::ENET_Normal) + X3DGeoHelper::add_normal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((X3DNodeElementNormal *)*ch_it)->Value, + tnemesh.NormalPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_TextureCoordinate) + X3DGeoHelper::add_tex_coord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((X3DNodeElementTextureCoordinate *)*ch_it)->Value); + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TrianlgeSet: " + ai_to_string((*ch_it)->Type) + "."); + } // for(std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == X3DElemType::ENET_TriangleSet) + + if (pNodeElement.Type == X3DElemType::ENET_TriangleStripSet) { + X3DNodeElementSet &tnemesh = *((X3DNodeElementSet *)&pNodeElement); // create alias for convenience + + // at first search for node and create mesh. + for (std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + *pMesh = X3DGeoHelper::make_mesh(tnemesh.CoordIndex, ((X3DNodeElementCoordinate *)*ch_it)->Value); + } + } + + // copy additional information from children + for (std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) { + ai_assert(*pMesh); + if ((*ch_it)->Type == X3DElemType::ENET_Color) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColor *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_ColorRGBA) + X3DGeoHelper::add_color(**pMesh, ((X3DNodeElementColorRGBA *)*ch_it)->Value, tnemesh.ColorPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_Coordinate) { + } // skip because already read when mesh created. + else if ((*ch_it)->Type == X3DElemType::ENET_Normal) + X3DGeoHelper::add_normal(**pMesh, tnemesh.CoordIndex, tnemesh.NormalIndex, ((X3DNodeElementNormal *)*ch_it)->Value, + tnemesh.NormalPerVertex); + else if ((*ch_it)->Type == X3DElemType::ENET_TextureCoordinate) + X3DGeoHelper::add_tex_coord(**pMesh, tnemesh.CoordIndex, tnemesh.TexCoordIndex, ((X3DNodeElementTextureCoordinate *)*ch_it)->Value); + else + throw DeadlyImportError("Postprocess_BuildMesh. Unknown child of TriangleStripSet: " + ai_to_string((*ch_it)->Type) + "."); + } // for(std::list::iterator ch_it = tnemesh.Children.begin(); ch_it != tnemesh.Children.end(); ++ch_it) + + return; // mesh is build, nothing to do anymore. + } // if(pNodeElement.Type == X3DElemType::ENET_TriangleStripSet) + + throw DeadlyImportError("Postprocess_BuildMesh. Unknown mesh type: " + ai_to_string(pNodeElement.Type) + "."); +} + +void X3DImporter::Postprocess_BuildNode(const X3DNodeElementBase &pNodeElement, aiNode &pSceneNode, std::list &pSceneMeshList, + std::list &pSceneMaterialList, std::list &pSceneLightList) const { + std::list::const_iterator chit_begin = pNodeElement.Children.begin(); + std::list::const_iterator chit_end = pNodeElement.Children.end(); + std::list SceneNode_Child; + std::list SceneNode_Mesh; + + // At first read all metadata + Postprocess_CollectMetadata(pNodeElement, pSceneNode); + // check if we have deal with grouping node. Which can contain transformation or switch + if (pNodeElement.Type == X3DElemType::ENET_Group) { + const X3DNodeElementGroup &tne_group = *((X3DNodeElementGroup *)&pNodeElement); // create alias for convenience + + pSceneNode.mTransformation = tne_group.Transformation; + if (tne_group.UseChoice) { + // If Choice is less than zero or greater than the number of nodes in the children field, nothing is chosen. + if ((tne_group.Choice < 0) || ((size_t)tne_group.Choice >= pNodeElement.Children.size())) { + chit_begin = pNodeElement.Children.end(); + chit_end = pNodeElement.Children.end(); + } else { + for (size_t i = 0; i < (size_t)tne_group.Choice; i++) + ++chit_begin; // forward iterator to chosen node. + + chit_end = chit_begin; + ++chit_end; // point end iterator to next element after chosen node. + } + } // if(tne_group.UseChoice) + } // if(pNodeElement.Type == X3DElemType::ENET_Group) + + // Reserve memory for fast access and check children. + for (std::list::const_iterator it = chit_begin; it != chit_end; ++it) { // in this loop we do not read metadata because it's already read at begin. + if ((*it)->Type == X3DElemType::ENET_Group) { + // if child is group then create new node and do recursive call. + aiNode *new_node = new aiNode; + + new_node->mName = (*it)->ID; + new_node->mParent = &pSceneNode; + SceneNode_Child.push_back(new_node); + Postprocess_BuildNode(**it, *new_node, pSceneMeshList, pSceneMaterialList, pSceneLightList); + } else if ((*it)->Type == X3DElemType::ENET_Shape) { + // shape can contain only one geometry and one appearance nodes. + Postprocess_BuildShape(*((X3DNodeElementShape *)*it), SceneNode_Mesh, pSceneMeshList, pSceneMaterialList); + } else if (((*it)->Type == X3DElemType::ENET_DirectionalLight) || ((*it)->Type == X3DElemType::ENET_PointLight) || + ((*it)->Type == X3DElemType::ENET_SpotLight)) { + Postprocess_BuildLight(*((X3DNodeElementLight *)*it), pSceneLightList); + } else if (!PostprocessHelper_ElementIsMetadata((*it)->Type)) // skip metadata + { + throw DeadlyImportError("Postprocess_BuildNode. Unknown type: " + ai_to_string((*it)->Type) + "."); + } + } // for(std::list::const_iterator it = chit_begin; it != chit_end; it++) + + // copy data about children and meshes to aiNode. + if (!SceneNode_Child.empty()) { + std::list::const_iterator it = SceneNode_Child.begin(); + + pSceneNode.mNumChildren = static_cast(SceneNode_Child.size()); + pSceneNode.mChildren = new aiNode *[pSceneNode.mNumChildren]; + for (size_t i = 0; i < pSceneNode.mNumChildren; i++) + pSceneNode.mChildren[i] = *it++; + } + + if (!SceneNode_Mesh.empty()) { + std::list::const_iterator it = SceneNode_Mesh.begin(); + + pSceneNode.mNumMeshes = static_cast(SceneNode_Mesh.size()); + pSceneNode.mMeshes = new unsigned int[pSceneNode.mNumMeshes]; + for (size_t i = 0; i < pSceneNode.mNumMeshes; i++) + pSceneNode.mMeshes[i] = *it++; + } + + // that's all. return to previous deals +} + +void X3DImporter::Postprocess_BuildShape(const X3DNodeElementShape &pShapeNodeElement, std::list &pNodeMeshInd, + std::list &pSceneMeshList, std::list &pSceneMaterialList) const { + aiMaterial *tmat = nullptr; + aiMesh *tmesh = nullptr; + X3DElemType mesh_type = X3DElemType::ENET_Invalid; + unsigned int mat_ind = 0; + + for (std::list::const_iterator it = pShapeNodeElement.Children.begin(); it != pShapeNodeElement.Children.end(); ++it) { + if (PostprocessHelper_ElementIsMesh((*it)->Type)) { + Postprocess_BuildMesh(**it, &tmesh); + if (tmesh != nullptr) { + // if mesh successfully built then add data about it to arrays + pNodeMeshInd.push_back(static_cast(pSceneMeshList.size())); + pSceneMeshList.push_back(tmesh); + // keep mesh type. Need above for texture coordinate generation. + mesh_type = (*it)->Type; + } + } else if ((*it)->Type == X3DElemType::ENET_Appearance) { + Postprocess_BuildMaterial(**it, &tmat); + if (tmat != nullptr) { + // if material successfully built then add data about it to array + mat_ind = static_cast(pSceneMaterialList.size()); + pSceneMaterialList.push_back(tmat); + } + } + } // for(std::list::const_iterator it = pShapeNodeElement.Children.begin(); it != pShapeNodeElement.Children.end(); it++) + + // associate read material with read mesh. + if ((tmesh != nullptr) && (tmat != nullptr)) { + tmesh->mMaterialIndex = mat_ind; + // Check texture mapping. If material has texture but mesh has no texture coordinate then try to ask Assimp to generate texture coordinates. + if ((tmat->GetTextureCount(aiTextureType_DIFFUSE) != 0) && !tmesh->HasTextureCoords(0)) { + int32_t tm; + aiVector3D tvec3; + + switch (mesh_type) { + case X3DElemType::ENET_Box: + tm = aiTextureMapping_BOX; + break; + case X3DElemType::ENET_Cone: + case X3DElemType::ENET_Cylinder: + tm = aiTextureMapping_CYLINDER; + break; + case X3DElemType::ENET_Sphere: + tm = aiTextureMapping_SPHERE; + break; + default: + tm = aiTextureMapping_PLANE; + break; + } // switch(mesh_type) + + tmat->AddProperty(&tm, 1, AI_MATKEY_MAPPING_DIFFUSE(0)); + } // if((tmat->GetTextureCount(aiTextureType_DIFFUSE) != 0) && !tmesh->HasTextureCoords(0)) + } // if((tmesh != nullptr) && (tmat != nullptr)) +} + +void X3DImporter::Postprocess_CollectMetadata(const X3DNodeElementBase &pNodeElement, aiNode &pSceneNode) const { + std::list meta_list; + size_t meta_idx; + + PostprocessHelper_CollectMetadata(pNodeElement, meta_list); // find metadata in current node element. + if (!meta_list.empty()) { + if (pSceneNode.mMetaData != nullptr) { + throw DeadlyImportError("Postprocess. MetaData member in node are not nullptr. Something went wrong."); + } + + // copy collected metadata to output node. + pSceneNode.mMetaData = aiMetadata::Alloc(static_cast(meta_list.size())); + meta_idx = 0; + for (std::list::const_iterator it = meta_list.begin(); it != meta_list.end(); ++it, ++meta_idx) { + X3DNodeElementMeta *cur_meta = (X3DNodeElementMeta *)*it; + + // due to limitations we can add only first element of value list. + // Add an element according to its type. + if ((*it)->Type == X3DElemType::ENET_MetaBoolean) { + if (((X3DNodeElementMetaBoolean *)cur_meta)->Value.size() > 0) + pSceneNode.mMetaData->Set(static_cast(meta_idx), cur_meta->Name, *(((X3DNodeElementMetaBoolean *)cur_meta)->Value.begin())); + } else if ((*it)->Type == X3DElemType::ENET_MetaDouble) { + if (((X3DNodeElementMetaDouble *)cur_meta)->Value.size() > 0) + pSceneNode.mMetaData->Set(static_cast(meta_idx), cur_meta->Name, (float)*(((X3DNodeElementMetaDouble *)cur_meta)->Value.begin())); + } else if ((*it)->Type == X3DElemType::ENET_MetaFloat) { + if (((X3DNodeElementMetaFloat *)cur_meta)->Value.size() > 0) + pSceneNode.mMetaData->Set(static_cast(meta_idx), cur_meta->Name, *(((X3DNodeElementMetaFloat *)cur_meta)->Value.begin())); + } else if ((*it)->Type == X3DElemType::ENET_MetaInteger) { + if (((X3DNodeElementMetaInt *)cur_meta)->Value.size() > 0) + pSceneNode.mMetaData->Set(static_cast(meta_idx), cur_meta->Name, *(((X3DNodeElementMetaInt *)cur_meta)->Value.begin())); + } else if ((*it)->Type == X3DElemType::ENET_MetaString) { + if (((X3DNodeElementMetaString *)cur_meta)->Value.size() > 0) { + aiString tstr(((X3DNodeElementMetaString *)cur_meta)->Value.begin()->data()); + + pSceneNode.mMetaData->Set(static_cast(meta_idx), cur_meta->Name, tstr); + } + } else { + throw DeadlyImportError("Postprocess. Unknown metadata type."); + } // if((*it)->Type == X3DElemType::ENET_Meta*) else + } // for(std::list::const_iterator it = meta_list.begin(); it != meta_list.end(); it++) + } // if( !meta_list.empty() ) +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/code/AssetLib/X3D/X3DImporter_Rendering.cpp b/code/AssetLib/X3D/X3DImporter_Rendering.cpp new file mode 100644 index 000000000..fe7e68081 --- /dev/null +++ b/code/AssetLib/X3D/X3DImporter_Rendering.cpp @@ -0,0 +1,987 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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. + +---------------------------------------------------------------------- +*/ +/// \file X3DImporter_Rendering.cpp +/// \brief Parsing data from nodes of "Rendering" set of X3D. +/// \date 2015-2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" +#include "X3DXmlHelper.h" + +namespace Assimp { + +// +void X3DImporter::readColor(XmlNode &node) { + std::string use, def; + std::list color; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getColor3DListAttribute(node, "color", color); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Color, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementColor(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementColor *)ne)->Value = color; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Color"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +void X3DImporter::readColorRGBA(XmlNode &node) { + std::string use, def; + std::list color; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getColor4DListAttribute(node, "color", color); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_ColorRGBA, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementColorRGBA(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementColorRGBA *)ne)->Value = color; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "ColorRGBA"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +void X3DImporter::readCoordinate(XmlNode &node) { + std::string use, def; + std::list point; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector3DListAttribute(node, "point", point); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Coordinate, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementCoordinate(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementCoordinate *)ne)->Value = point; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Coordinate"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +// +// ColorCoordinateContentModel is the child-node content model corresponding to IndexedLineSet, LineSet and PointSet. ColorCoordinateContentModel can +// contain any-order Coordinate node with Color (or ColorRGBA) node. No more than one instance of any single node type is allowed. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +void X3DImporter::readIndexedLineSet(XmlNode &node) { + std::string use, def; + std::vector colorIndex; + bool colorPerVertex = true; + std::vector coordIndex; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getInt32ArrayAttribute(node, "colorIndex", colorIndex); + XmlParser::getBoolAttribute(node, "colorPerVertex", colorPerVertex); + X3DXmlHelper::getInt32ArrayAttribute(node, "coordIndex", coordIndex); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedLineSet, ne); + } else { + // check data + if ((coordIndex.size() < 2) || ((coordIndex.back() == (-1)) && (coordIndex.size() < 3))) + throw DeadlyImportError("IndexedLineSet must contain not empty \"coordIndex\" attribute."); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_IndexedLineSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementIndexedSet &ne_alias = *((X3DNodeElementIndexedSet *)ne); + + ne_alias.ColorIndex = colorIndex; + ne_alias.ColorPerVertex = colorPerVertex; + ne_alias.CoordIndex = coordIndex; + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for Color and Coordinate nodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("IndexedLineSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +// +// ComposedGeometryContentModel is the child-node content model corresponding to X3DComposedGeometryNodes. It can contain Color (or ColorRGBA), Coordinate, +// Normal and TextureCoordinate, in any order. No more than one instance of these nodes is allowed. Multiple VertexAttribute (FloatVertexAttribute, +// Matrix3VertexAttribute, Matrix4VertexAttribute) nodes can also be contained. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +void X3DImporter::readIndexedTriangleFanSet(XmlNode &node) { + std::string use, def; + bool ccw = true; + bool colorPerVertex = true; + std::vector index; + bool normalPerVertex = true; + bool solid = true; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "ccw", ccw); + XmlParser::getBoolAttribute(node, "colorPerVertex", colorPerVertex); + X3DXmlHelper::getInt32ArrayAttribute(node, "index", index); + XmlParser::getBoolAttribute(node, "normalPerVertex", normalPerVertex); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedTriangleFanSet, ne); + } else { + // check data + if (index.size() == 0) throw DeadlyImportError("IndexedTriangleFanSet must contain not empty \"index\" attribute."); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_IndexedTriangleFanSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementIndexedSet &ne_alias = *((X3DNodeElementIndexedSet *)ne); + + ne_alias.CCW = ccw; + ne_alias.ColorPerVertex = colorPerVertex; + ne_alias.NormalPerVertex = normalPerVertex; + ne_alias.Solid = solid; + + ne_alias.CoordIndex.clear(); + int counter = 0; + int32_t idx[3]; + for (std::vector::const_iterator idx_it = index.begin(); idx_it != index.end(); ++idx_it) { + idx[2] = *idx_it; + if (idx[2] < 0) { + counter = 0; + } else { + if (counter >= 2) { + if (ccw) { + ne_alias.CoordIndex.push_back(idx[0]); + ne_alias.CoordIndex.push_back(idx[1]); + ne_alias.CoordIndex.push_back(idx[2]); + } else { + ne_alias.CoordIndex.push_back(idx[0]); + ne_alias.CoordIndex.push_back(idx[2]); + ne_alias.CoordIndex.push_back(idx[1]); + } + ne_alias.CoordIndex.push_back(-1); + idx[1] = idx[2]; + } else { + idx[counter] = idx[2]; + } + ++counter; + } + } // for(std::list::const_iterator idx_it = index.begin(); idx_it != ne_alias.index.end(); idx_it++) + + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + else if (currentChildName == "Normal") + readNormal(currentChildNode); + else if (currentChildName == "TextureCoordinate") + readTextureCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("IndexedTriangleFanSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +// +// ComposedGeometryContentModel is the child-node content model corresponding to X3DComposedGeometryNodes. It can contain Color (or ColorRGBA), Coordinate, +// Normal and TextureCoordinate, in any order. No more than one instance of these nodes is allowed. Multiple VertexAttribute (FloatVertexAttribute, +// Matrix3VertexAttribute, Matrix4VertexAttribute) nodes can also be contained. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +void X3DImporter::readIndexedTriangleSet(XmlNode &node) { + std::string use, def; + bool ccw = true; + bool colorPerVertex = true; + std::vector index; + bool normalPerVertex = true; + bool solid = true; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "ccw", ccw); + XmlParser::getBoolAttribute(node, "colorPerVertex", colorPerVertex); + X3DXmlHelper::getInt32ArrayAttribute(node, "index", index); + XmlParser::getBoolAttribute(node, "normalPerVertex", normalPerVertex); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedTriangleSet, ne); + } else { + // check data + if (index.size() == 0) throw DeadlyImportError("IndexedTriangleSet must contain not empty \"index\" attribute."); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_IndexedTriangleSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementIndexedSet &ne_alias = *((X3DNodeElementIndexedSet *)ne); + + ne_alias.CCW = ccw; + ne_alias.ColorPerVertex = colorPerVertex; + ne_alias.NormalPerVertex = normalPerVertex; + ne_alias.Solid = solid; + + ne_alias.CoordIndex.clear(); + int counter = 0; + int32_t idx[3]; + for (std::vector::const_iterator idx_it = index.begin(); idx_it != index.end(); ++idx_it) { + idx[counter++] = *idx_it; + if (counter > 2) { + counter = 0; + if (ccw) { + ne_alias.CoordIndex.push_back(idx[0]); + ne_alias.CoordIndex.push_back(idx[1]); + ne_alias.CoordIndex.push_back(idx[2]); + } else { + ne_alias.CoordIndex.push_back(idx[0]); + ne_alias.CoordIndex.push_back(idx[2]); + ne_alias.CoordIndex.push_back(idx[1]); + } + ne_alias.CoordIndex.push_back(-1); + } + } // for(std::list::const_iterator idx_it = index.begin(); idx_it != ne_alias.index.end(); idx_it++) + + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + else if (currentChildName == "Normal") + readNormal(currentChildNode); + else if (currentChildName == "TextureCoordinate") + readTextureCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("IndexedTriangleSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +// +// ComposedGeometryContentModel is the child-node content model corresponding to X3DComposedGeometryNodes. It can contain Color (or ColorRGBA), Coordinate, +// Normal and TextureCoordinate, in any order. No more than one instance of these nodes is allowed. Multiple VertexAttribute (FloatVertexAttribute, +// Matrix3VertexAttribute, Matrix4VertexAttribute) nodes can also be contained. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +void X3DImporter::readIndexedTriangleStripSet(XmlNode &node) { + std::string use, def; + bool ccw = true; + bool colorPerVertex = true; + std::vector index; + bool normalPerVertex = true; + bool solid = true; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "ccw", ccw); + XmlParser::getBoolAttribute(node, "colorPerVertex", colorPerVertex); + X3DXmlHelper::getInt32ArrayAttribute(node, "index", index); + XmlParser::getBoolAttribute(node, "normalPerVertex", normalPerVertex); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedTriangleStripSet, ne); + } else { + // check data + if (index.size() == 0) throw DeadlyImportError("IndexedTriangleStripSet must contain not empty \"index\" attribute."); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_IndexedTriangleStripSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementIndexedSet &ne_alias = *((X3DNodeElementIndexedSet *)ne); + + ne_alias.CCW = ccw; + ne_alias.ColorPerVertex = colorPerVertex; + ne_alias.NormalPerVertex = normalPerVertex; + ne_alias.Solid = solid; + + ne_alias.CoordIndex.clear(); + int counter = 0; + int32_t idx[3]; + for (std::vector::const_iterator idx_it = index.begin(); idx_it != index.end(); ++idx_it) { + idx[2] = *idx_it; + if (idx[2] < 0) { + counter = 0; + } else { + if (counter >= 2) { + if (ccw) { + ne_alias.CoordIndex.push_back(idx[0]); + ne_alias.CoordIndex.push_back(idx[1]); + ne_alias.CoordIndex.push_back(idx[2]); + } else { + ne_alias.CoordIndex.push_back(idx[0]); + ne_alias.CoordIndex.push_back(idx[2]); + ne_alias.CoordIndex.push_back(idx[1]); + } + ne_alias.CoordIndex.push_back(-1); + } + idx[counter & 1] = idx[2]; + ++counter; + } + } // for(std::list::const_iterator idx_it = index.begin(); idx_it != ne_alias.index.end(); idx_it++) + + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + else if (currentChildName == "Normal") + readNormal(currentChildNode); + else if (currentChildName == "TextureCoordinate") + readTextureCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("IndexedTriangleStripSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +// +// ColorCoordinateContentModel is the child-node content model corresponding to IndexedLineSet, LineSet and PointSet. ColorCoordinateContentModel can +// contain any-order Coordinate node with Color (or ColorRGBA) node. No more than one instance of any single node type is allowed. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +void X3DImporter::readLineSet(XmlNode &node) { + std::string use, def; + std::vector vertexCount; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getInt32ArrayAttribute(node, "vertexCount", vertexCount); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_LineSet, ne); + } else { + // check data + if (vertexCount.size() == 0) throw DeadlyImportError("LineSet must contain not empty \"vertexCount\" attribute."); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementSet(X3DElemType::ENET_LineSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementSet &ne_alias = *((X3DNodeElementSet *)ne); + + ne_alias.VertexCount = vertexCount; + // create CoordIdx + size_t coord_num = 0; + + ne_alias.CoordIndex.clear(); + for (std::vector::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); ++vc_it) { + if (*vc_it < 2) throw DeadlyImportError("LineSet. vertexCount shall be greater than or equal to two."); + + for (int32_t i = 0; i < *vc_it; i++) + ne_alias.CoordIndex.push_back(static_cast(coord_num++)); // add vertices indices + + ne_alias.CoordIndex.push_back(-1); // add face delimiter. + } + + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("LineSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +// +// ColorCoordinateContentModel is the child-node content model corresponding to IndexedLineSet, LineSet and PointSet. ColorCoordinateContentModel can +// contain any-order Coordinate node with Color (or ColorRGBA) node. No more than one instance of any single node type is allowed. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +void X3DImporter::readPointSet(XmlNode &node) { + std::string use, def; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_PointSet, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_PointSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("PointSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +// +// ComposedGeometryContentModel is the child-node content model corresponding to X3DComposedGeometryNodes. It can contain Color (or ColorRGBA), Coordinate, +// Normal and TextureCoordinate, in any order. No more than one instance of these nodes is allowed. Multiple VertexAttribute (FloatVertexAttribute, +// Matrix3VertexAttribute, Matrix4VertexAttribute) nodes can also be contained. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +void X3DImporter::readTriangleFanSet(XmlNode &node) { + std::string use, def; + bool ccw = true; + bool colorPerVertex = true; + std::vector fanCount; + bool normalPerVertex = true; + bool solid = true; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "ccw", ccw); + XmlParser::getBoolAttribute(node, "colorPerVertex", colorPerVertex); + X3DXmlHelper::getInt32ArrayAttribute(node, "fanCount", fanCount); + XmlParser::getBoolAttribute(node, "normalPerVertex", normalPerVertex); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TriangleFanSet, ne); + } else { + // check data + if (fanCount.size() == 0) throw DeadlyImportError("TriangleFanSet must contain not empty \"fanCount\" attribute."); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementSet(X3DElemType::ENET_TriangleFanSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementSet &ne_alias = *((X3DNodeElementSet *)ne); + + ne_alias.CCW = ccw; + ne_alias.ColorPerVertex = colorPerVertex; + ne_alias.VertexCount = fanCount; + ne_alias.NormalPerVertex = normalPerVertex; + ne_alias.Solid = solid; + // create CoordIdx + size_t coord_num_first, coord_num_prev; + + ne_alias.CoordIndex.clear(); + // assign indices for first triangle + coord_num_first = 0; + coord_num_prev = 1; + for (std::vector::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); ++vc_it) { + if (*vc_it < 3) throw DeadlyImportError("TriangleFanSet. fanCount shall be greater than or equal to three."); + + for (int32_t vc = 2; vc < *vc_it; vc++) { + if (ccw) { + // 2 1 + // 0 + ne_alias.CoordIndex.push_back(static_cast(coord_num_first)); // first vertex is a center and always is [0]. + ne_alias.CoordIndex.push_back(static_cast(coord_num_prev++)); + ne_alias.CoordIndex.push_back(static_cast(coord_num_prev)); + } else { + // 1 2 + // 0 + ne_alias.CoordIndex.push_back(static_cast(coord_num_first)); // first vertex is a center and always is [0]. + ne_alias.CoordIndex.push_back(static_cast(coord_num_prev + 1)); + ne_alias.CoordIndex.push_back(static_cast(coord_num_prev++)); + } // if(ccw) else + + ne_alias.CoordIndex.push_back(-1); // add face delimiter. + } // for(int32_t vc = 2; vc < *vc_it; vc++) + + coord_num_prev++; // that index will be center of next fan + coord_num_first = coord_num_prev++; // forward to next point - second point of fan + } // for(std::list::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); vc_it++) + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + else if (currentChildName == "Normal") + readNormal(currentChildNode); + else if (currentChildName == "TextureCoordinate") + readTextureCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("TriangleFanSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +// +// ComposedGeometryContentModel is the child-node content model corresponding to X3DComposedGeometryNodes. It can contain Color (or ColorRGBA), Coordinate, +// Normal and TextureCoordinate, in any order. No more than one instance of these nodes is allowed. Multiple VertexAttribute (FloatVertexAttribute, +// Matrix3VertexAttribute, Matrix4VertexAttribute) nodes can also be contained. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +void X3DImporter::readTriangleSet(XmlNode &node) { + std::string use, def; + bool ccw = true; + bool colorPerVertex = true; + bool normalPerVertex = true; + bool solid = true; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "ccw", ccw); + XmlParser::getBoolAttribute(node, "colorPerVertex", colorPerVertex); + XmlParser::getBoolAttribute(node, "normalPerVertex", normalPerVertex); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TriangleSet, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_TriangleSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementSet &ne_alias = *((X3DNodeElementSet *)ne); + + ne_alias.CCW = ccw; + ne_alias.ColorPerVertex = colorPerVertex; + ne_alias.NormalPerVertex = normalPerVertex; + ne_alias.Solid = solid; + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + else if (currentChildName == "Normal") + readNormal(currentChildNode); + else if (currentChildName == "TextureCoordinate") + readTextureCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("TriangleSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +// +// ComposedGeometryContentModel is the child-node content model corresponding to X3DComposedGeometryNodes. It can contain Color (or ColorRGBA), Coordinate, +// Normal and TextureCoordinate, in any order. No more than one instance of these nodes is allowed. Multiple VertexAttribute (FloatVertexAttribute, +// Matrix3VertexAttribute, Matrix4VertexAttribute) nodes can also be contained. +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model. +// +void X3DImporter::readTriangleStripSet(XmlNode &node) { + std::string use, def; + bool ccw = true; + bool colorPerVertex = true; + std::vector stripCount; + bool normalPerVertex = true; + bool solid = true; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "ccw", ccw); + XmlParser::getBoolAttribute(node, "colorPerVertex", colorPerVertex); + X3DXmlHelper::getInt32ArrayAttribute(node, "stripCount", stripCount); + XmlParser::getBoolAttribute(node, "normalPerVertex", normalPerVertex); + XmlParser::getBoolAttribute(node, "solid", solid); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TriangleStripSet, ne); + } else { + // check data + if (stripCount.size() == 0) throw DeadlyImportError("TriangleStripSet must contain not empty \"stripCount\" attribute."); + + // create and if needed - define new geometry object. + ne = new X3DNodeElementSet(X3DElemType::ENET_TriangleStripSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; + + X3DNodeElementSet &ne_alias = *((X3DNodeElementSet *)ne); + + ne_alias.CCW = ccw; + ne_alias.ColorPerVertex = colorPerVertex; + ne_alias.VertexCount = stripCount; + ne_alias.NormalPerVertex = normalPerVertex; + ne_alias.Solid = solid; + // create CoordIdx + size_t coord_num0, coord_num1, coord_num2; // indices of current triangle + bool odd_tri; // sequence of current triangle + size_t coord_num_sb; // index of first point of strip + + ne_alias.CoordIndex.clear(); + coord_num_sb = 0; + for (std::vector::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); ++vc_it) { + if (*vc_it < 3) throw DeadlyImportError("TriangleStripSet. stripCount shall be greater than or equal to three."); + + // set initial values for first triangle + coord_num0 = coord_num_sb; + coord_num1 = coord_num_sb + 1; + coord_num2 = coord_num_sb + 2; + odd_tri = true; + + for (int32_t vc = 2; vc < *vc_it; vc++) { + if (ccw) { + // 0 2 + // 1 + ne_alias.CoordIndex.push_back(static_cast(coord_num0)); + ne_alias.CoordIndex.push_back(static_cast(coord_num1)); + ne_alias.CoordIndex.push_back(static_cast(coord_num2)); + } else { + // 0 1 + // 2 + ne_alias.CoordIndex.push_back(static_cast(coord_num0)); + ne_alias.CoordIndex.push_back(static_cast(coord_num2)); + ne_alias.CoordIndex.push_back(static_cast(coord_num1)); + } // if(ccw) else + + ne_alias.CoordIndex.push_back(-1); // add face delimiter. + // prepare values for next triangle + if (odd_tri) { + coord_num0 = coord_num2; + coord_num2++; + } else { + coord_num1 = coord_num2; + coord_num2 = coord_num1 + 1; + } + + odd_tri = !odd_tri; + coord_num_sb = coord_num2; // that index will be start of next strip + } // for(int32_t vc = 2; vc < *vc_it; vc++) + } // for(std::list::const_iterator vc_it = ne_alias.VertexCount.begin(); vc_it != ne_alias.VertexCount.end(); vc_it++) + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + else if (currentChildName == "Normal") + readNormal(currentChildNode); + else if (currentChildName == "TextureCoordinate") + readTextureCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("TriangleStripSet", currentChildNode); + } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +void X3DImporter::readNormal(XmlNode &node) { + std::string use, def; + std::list vector; + X3DNodeElementBase *ne; + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector3DListAttribute(node, "vector", vector); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Normal, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementNormal(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementNormal *)ne)->Value = vector; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Normal"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/code/AssetLib/X3D/X3DImporter_Shape.cpp b/code/AssetLib/X3D/X3DImporter_Shape.cpp new file mode 100644 index 000000000..7a292be5c --- /dev/null +++ b/code/AssetLib/X3D/X3DImporter_Shape.cpp @@ -0,0 +1,221 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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. + +---------------------------------------------------------------------- +*/ +/// \file X3DImporter_Shape.cpp +/// \brief Parsing data from nodes of "Shape" set of X3D. +/// \date 2015-2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" +#include "X3DXmlHelper.h" + +namespace Assimp { + +void X3DImporter::readShape(XmlNode &node) { + std::string use, def; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Shape, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementShape(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + // check for appearance node + if (currentChildName == "Appearance") readAppearance(currentChildNode); + // check for X3DGeometryNodes + else if (currentChildName == "Arc2D") readArc2D(currentChildNode); + else if (currentChildName == "ArcClose2D") readArcClose2D(currentChildNode); + else if (currentChildName == "Circle2D") readCircle2D(currentChildNode); + else if (currentChildName == "Disk2D") readDisk2D(currentChildNode); + else if (currentChildName == "Polyline2D") readPolyline2D(currentChildNode); + else if (currentChildName == "Polypoint2D") readPolypoint2D(currentChildNode); + else if (currentChildName == "Rectangle2D") readRectangle2D(currentChildNode); + else if (currentChildName == "TriangleSet2D") readTriangleSet2D(currentChildNode); + else if (currentChildName == "Box") readBox(currentChildNode); + else if (currentChildName == "Cone") readCone(currentChildNode); + else if (currentChildName == "Cylinder") readCylinder(currentChildNode); + else if (currentChildName == "ElevationGrid") readElevationGrid(currentChildNode); + else if (currentChildName == "Extrusion") readExtrusion(currentChildNode); + else if (currentChildName == "IndexedFaceSet") readIndexedFaceSet(currentChildNode); + else if (currentChildName == "Sphere") readSphere(currentChildNode); + else if (currentChildName == "IndexedLineSet") readIndexedLineSet(currentChildNode); + else if (currentChildName == "LineSet") readLineSet(currentChildNode); + else if (currentChildName == "PointSet") readPointSet(currentChildNode); + else if (currentChildName == "IndexedTriangleFanSet") readIndexedTriangleFanSet(currentChildNode); + else if (currentChildName == "IndexedTriangleSet") readIndexedTriangleSet(currentChildNode); + else if (currentChildName == "IndexedTriangleStripSet") readIndexedTriangleStripSet(currentChildNode); + else if (currentChildName == "TriangleFanSet") readTriangleFanSet(currentChildNode); + else if (currentChildName == "TriangleSet") readTriangleSet(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) skipUnsupportedNode("Shape", currentChildNode); + } + + ParseHelper_Node_Exit(); + } // if (!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +// +// "Child-node content model corresponding to X3DAppearanceChildNode. Appearance can contain FillProperties, LineProperties, Material, any Texture node and +// any TextureTransform node, in any order. No more than one instance of these nodes is allowed. Appearance may also contain multiple shaders (ComposedShader, +// PackagedShader, ProgramShader). +// A ProtoInstance node (with the proper node type) can be substituted for any node in this content model." +// +void X3DImporter::readAppearance(XmlNode &node) { + std::string use, def; + X3DNodeElementBase* ne( nullptr ); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + + // if "USE" defined then find already defined element. + if (!use.empty()) + { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Appearance, ne); + } + else + { + // create and if needed - define new geometry object. + ne = new X3DNodeElementAppearance(mNodeElementCur); + if(!def.empty()) ne->ID = def; + + // check for child nodes + if(!isNodeEmpty(node)) + { + ParseHelper_Node_Enter(ne); + for (auto currentChildNode : node.children()) { + const std::string ¤tChildName = currentChildNode.name(); + if (currentChildName == "Material") readMaterial(currentChildNode); + else if (currentChildName == "ImageTexture") readImageTexture(currentChildNode); + else if (currentChildName == "TextureTransform") readTextureTransform(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) skipUnsupportedNode("Appearance", currentChildNode); + } + ParseHelper_Node_Exit(); + }// if(!isNodeEmpty(node)) + else + { + mNodeElementCur->Children.push_back(ne);// add made object as child to current element + } + + NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph + }// if(!use.empty()) else +} + +// +void X3DImporter::readMaterial(XmlNode &node) { + std::string use, def; + float ambientIntensity = 0.2f; + float shininess = 0.2f; + float transparency = 0; + aiColor3D diffuseColor(0.8f, 0.8f, 0.8f); + aiColor3D emissiveColor(0, 0, 0); + aiColor3D specularColor(0, 0, 0); + X3DNodeElementBase* ne( nullptr ); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getFloatAttribute(node, "ambientIntensity", ambientIntensity); + XmlParser::getFloatAttribute(node, "shininess", shininess); + XmlParser::getFloatAttribute(node, "transparency", transparency); + X3DXmlHelper::getColor3DAttribute(node, "diffuseColor", diffuseColor); + X3DXmlHelper::getColor3DAttribute(node, "emissiveColor", emissiveColor); + X3DXmlHelper::getColor3DAttribute(node, "specularColor", specularColor); + + // if "USE" defined then find already defined element. + if(!use.empty()) + { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Material, ne); + } + else + { + // create and if needed - define new geometry object. + ne = new X3DNodeElementMaterial(mNodeElementCur); + if(!def.empty()) ne->ID = def; + + ((X3DNodeElementMaterial *)ne)->AmbientIntensity = ambientIntensity; + ((X3DNodeElementMaterial *)ne)->Shininess = shininess; + ((X3DNodeElementMaterial *)ne)->Transparency = transparency; + ((X3DNodeElementMaterial *)ne)->DiffuseColor = diffuseColor; + ((X3DNodeElementMaterial *)ne)->EmissiveColor = emissiveColor; + ((X3DNodeElementMaterial *)ne)->SpecularColor = specularColor; + // check for child nodes + if(!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Material"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph + }// if(!use.empty()) else +} + +}// namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/code/AssetLib/X3D/X3DImporter_Texturing.cpp b/code/AssetLib/X3D/X3DImporter_Texturing.cpp new file mode 100644 index 000000000..6463e2808 --- /dev/null +++ b/code/AssetLib/X3D/X3DImporter_Texturing.cpp @@ -0,0 +1,179 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, 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. + +---------------------------------------------------------------------- +*/ +/// \file X3DImporter_Texturing.cpp +/// \brief Parsing data from nodes of "Texturing" set of X3D. +/// \date 2015-2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER + +#include "X3DImporter.hpp" +#include "X3DImporter_Macro.hpp" +#include "X3DXmlHelper.h" + +namespace Assimp { + +// +// When the url field contains no values ([]), texturing is disabled. +void X3DImporter::readImageTexture(XmlNode &node) { + std::string use, def; + bool repeatS = true; + bool repeatT = true; + std::list url; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + XmlParser::getBoolAttribute(node, "repeatS", repeatS); + XmlParser::getBoolAttribute(node, "repeatT", repeatT); + X3DXmlHelper::getStringListAttribute(node, "url", url); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_ImageTexture, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementImageTexture(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementImageTexture *)ne)->RepeatS = repeatS; + ((X3DNodeElementImageTexture *)ne)->RepeatT = repeatT; + // Attribute "url" can contain list of strings. But we need only one - first. + if (!url.empty()) + ((X3DNodeElementImageTexture *)ne)->URL = url.front(); + else + ((X3DNodeElementImageTexture *)ne)->URL = ""; + + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "ImageTexture"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +void X3DImporter::readTextureCoordinate(XmlNode &node) { + std::string use, def; + std::list point; + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector2DListAttribute(node, "point", point); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TextureCoordinate, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementTextureCoordinate(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementTextureCoordinate *)ne)->Value = point; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "TextureCoordinate"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +// +void X3DImporter::readTextureTransform(XmlNode &node) { + std::string use, def; + aiVector2D center(0, 0); + float rotation = 0; + aiVector2D scale(1, 1); + aiVector2D translation(0, 0); + X3DNodeElementBase *ne(nullptr); + + MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); + X3DXmlHelper::getVector2DAttribute(node, "center", center); + XmlParser::getFloatAttribute(node, "rotation", rotation); + X3DXmlHelper::getVector2DAttribute(node, "scale", scale); + X3DXmlHelper::getVector2DAttribute(node, "translation", translation); + + // if "USE" defined then find already defined element. + if (!use.empty()) { + MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TextureTransform, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementTextureTransform(mNodeElementCur); + if (!def.empty()) ne->ID = def; + + ((X3DNodeElementTextureTransform *)ne)->Center = center; + ((X3DNodeElementTextureTransform *)ne)->Rotation = rotation; + ((X3DNodeElementTextureTransform *)ne)->Scale = scale; + ((X3DNodeElementTextureTransform *)ne)->Translation = translation; + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "TextureTransform"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else +} + +} // namespace Assimp + +#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/code/AssetLib/X3D/X3DXmlHelper.cpp b/code/AssetLib/X3D/X3DXmlHelper.cpp new file mode 100644 index 000000000..ff24b74b3 --- /dev/null +++ b/code/AssetLib/X3D/X3DXmlHelper.cpp @@ -0,0 +1,294 @@ +#include "X3DXmlHelper.h" +#include "X3DImporter.hpp" + +#include + +namespace Assimp { + +bool X3DXmlHelper::getColor3DAttribute(XmlNode &node, const char *attributeName, aiColor3D &color) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector values; + tokenize(val, values, " "); + if (values.size() != 3) { + Throw_ConvertFail_Str2ArrF(node.name(), attributeName); + return false; + } + auto it = values.begin(); + color.r = stof(*it++); + color.g = stof(*it++); + color.b = stof(*it); + return true; + } + return false; +} + +bool X3DXmlHelper::getVector2DAttribute(XmlNode &node, const char *attributeName, aiVector2D &color) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector values; + tokenize(val, values, " "); + if (values.size() != 2) { + Throw_ConvertFail_Str2ArrF(node.name(), attributeName); + return false; + } + auto it = values.begin(); + color.x = stof(*it++); + color.y = stof(*it); + return true; + } + return false; +} + +bool X3DXmlHelper::getVector3DAttribute(XmlNode &node, const char *attributeName, aiVector3D &color) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector values; + tokenize(val, values, " "); + if (values.size() != 3) { + Throw_ConvertFail_Str2ArrF(node.name(), attributeName); + return false; + } + auto it = values.begin(); + color.x = stof(*it++); + color.y = stof(*it++); + color.z = stof(*it); + return true; + } + return false; +} + +bool X3DXmlHelper::getBooleanArrayAttribute(XmlNode &node, const char *attributeName, std::vector &boolArray) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector values; + tokenize(val, values, " "); + auto it = values.begin(); + while (it != values.end()) { + auto s = *it++; + if (!s.empty()) + boolArray.push_back(s[0] == 't' || s[0] == '1'); + else + Throw_ConvertFail_Str2ArrB(node.name(), attributeName); + } + return true; + } + return false; +} + +bool X3DXmlHelper::getDoubleArrayAttribute(XmlNode &node, const char *attributeName, std::vector &doubleArray) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector values; + tokenize(val, values, " "); + auto it = values.begin(); + while (it != values.end()) { + auto s = *it++; + if (!s.empty()) + doubleArray.push_back(atof(s.c_str())); + else + Throw_ConvertFail_Str2ArrD(node.name(), attributeName); + } + return true; + } + return false; +} + +bool X3DXmlHelper::getFloatArrayAttribute(XmlNode &node, const char *attributeName, std::vector &floatArray) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector values; + tokenize(val, values, " "); + auto it = values.begin(); + while (it != values.end()) { + auto s = *it++; + if (!s.empty()) + floatArray.push_back((float)atof(s.c_str())); + else + Throw_ConvertFail_Str2ArrF(node.name(), attributeName); + } + return true; + } + return false; +} + +bool X3DXmlHelper::getInt32ArrayAttribute(XmlNode &node, const char *attributeName, std::vector &intArray) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector values; + tokenize(val, values, " "); + auto it = values.begin(); + while (it != values.end()) { + auto s = *it++; + if (!s.empty()) + intArray.push_back((int32_t)atof(s.c_str())); + else + Throw_ConvertFail_Str2ArrI(node.name(), attributeName); + } + return true; + } + return false; +} + +bool X3DXmlHelper::getStringListAttribute(XmlNode &node, const char *attributeName, std::list &stringList) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector values; + tokenize(val, values, " "); + auto it = values.begin(); + std::string currentConcat = ""; + bool inQuotes = false; + while (it != values.end()) { + auto s = *it++; + if (!s.empty()) { + if (inQuotes) { + if (*(s.rbegin()) == '"') { + stringList.push_back(currentConcat + s.substr(0, s.length() - 1)); + currentConcat = ""; + inQuotes = false; + } else { + currentConcat += " " + s; + } + } else { + if (s[0] == '"') { + currentConcat = s.substr(1); + inQuotes = true; + } else { + stringList.push_back(s); + } + } + } else if (!inQuotes) + Throw_ConvertFail_Str2ArrI(node.name(), attributeName); + } + if (inQuotes) Throw_ConvertFail_Str2ArrI(node.name(), attributeName); + return true; + } + return false; +} + +bool X3DXmlHelper::getStringArrayAttribute(XmlNode &node, const char *attributeName, std::vector &stringArray) { + std::list tlist; + + if (getStringListAttribute(node, attributeName, tlist)) { + if (!tlist.empty()) { + stringArray.reserve(tlist.size()); + for (std::list::iterator it = tlist.begin(); it != tlist.end(); ++it) { + stringArray.push_back(*it); + } + return true; + } + } + return false; +} + +bool X3DXmlHelper::getVector2DListAttribute(XmlNode &node, const char *attributeName, std::list &vectorList) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector values; + tokenize(val, values, " "); + if (values.size() % 2) Throw_ConvertFail_Str2ArrF(node.name(), attributeName); + auto it = values.begin(); + while (it != values.end()) { + aiVector2D tvec; + + tvec.x = (float)atof((*it++).c_str()); + tvec.y = (float)atof((*it++).c_str()); + vectorList.push_back(tvec); + } + return true; + } + return false; +} + +bool X3DXmlHelper::getVector2DArrayAttribute(XmlNode &node, const char *attributeName, std::vector &vectorArray) { + std::list tlist; + + if (getVector2DListAttribute(node, attributeName, tlist)) { + if (!tlist.empty()) { + vectorArray.reserve(tlist.size()); + for (std::list::iterator it = tlist.begin(); it != tlist.end(); ++it) { + vectorArray.push_back(*it); + } + return true; + } + } + return false; +} + +bool X3DXmlHelper::getVector3DListAttribute(XmlNode &node, const char *attributeName, std::list &vectorList) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector values; + tokenize(val, values, " "); + if (values.size() % 3 != 0) Throw_ConvertFail_Str2ArrF(node.name(), attributeName); + auto it = values.begin(); + while (it != values.end()) { + aiVector3D tvec; + + tvec.x = (float)atof((*it++).c_str()); + tvec.y = (float)atof((*it++).c_str()); + tvec.z = (float)atof((*it++).c_str()); + vectorList.push_back(tvec); + } + return true; + } + return false; +} + +bool X3DXmlHelper::getVector3DArrayAttribute(XmlNode &node, const char *attributeName, std::vector &vectorArray) { + std::list tlist; + + if (getVector3DListAttribute(node, attributeName, tlist)) { + if (!tlist.empty()) { + vectorArray.reserve(tlist.size()); + for (std::list::iterator it = tlist.begin(); it != tlist.end(); ++it) { + vectorArray.push_back(*it); + } + return true; + } + } + return false; +} + +bool X3DXmlHelper::getColor3DListAttribute(XmlNode &node, const char *attributeName, std::list &colorList) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector values; + tokenize(val, values, " "); + if (values.size() % 3 != 0) Throw_ConvertFail_Str2ArrF(node.name(), attributeName); + auto it = values.begin(); + while (it != values.end()) { + aiColor3D tvec; + + tvec.r = (float)atof((*it++).c_str()); + tvec.g = (float)atof((*it++).c_str()); + tvec.b = (float)atof((*it++).c_str()); + colorList.push_back(tvec); + } + return true; + } + return false; +} + +bool X3DXmlHelper::getColor4DListAttribute(XmlNode &node, const char *attributeName, std::list &colorList) { + std::string val; + if (XmlParser::getStdStrAttribute(node, attributeName, val)) { + std::vector values; + tokenize(val, values, " "); + if (values.size() % 4 != 0) Throw_ConvertFail_Str2ArrF(node.name(), attributeName); + auto it = values.begin(); + while (it != values.end()) { + aiColor4D tvec; + + tvec.r = (float)atof((*it++).c_str()); + tvec.g = (float)atof((*it++).c_str()); + tvec.b = (float)atof((*it++).c_str()); + tvec.a = (float)atof((*it++).c_str()); + colorList.push_back(tvec); + } + return true; + } + return false; +} + +} // namespace Assimp diff --git a/code/AssetLib/X3D/X3DXmlHelper.h b/code/AssetLib/X3D/X3DXmlHelper.h new file mode 100644 index 000000000..dd305f883 --- /dev/null +++ b/code/AssetLib/X3D/X3DXmlHelper.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +namespace Assimp { + +class X3DXmlHelper { +public: + static bool getColor3DAttribute(XmlNode &node, const char *attributeName, aiColor3D &color); + static bool getVector2DAttribute(XmlNode &node, const char *attributeName, aiVector2D &vector); + static bool getVector3DAttribute(XmlNode &node, const char *attributeName, aiVector3D &vector); + + static bool getBooleanArrayAttribute(XmlNode &node, const char *attributeName, std::vector &boolArray); + static bool getDoubleArrayAttribute(XmlNode &node, const char *attributeName, std::vector &doubleArray); + static bool getFloatArrayAttribute(XmlNode &node, const char *attributeName, std::vector &floatArray); + static bool getInt32ArrayAttribute(XmlNode &node, const char *attributeName, std::vector &intArray); + static bool getStringListAttribute(XmlNode &node, const char *attributeName, std::list &stringArray); + static bool getStringArrayAttribute(XmlNode &node, const char *attributeName, std::vector &stringArray); + + static bool getVector2DListAttribute(XmlNode &node, const char *attributeName, std::list &vectorList); + static bool getVector2DArrayAttribute(XmlNode &node, const char *attributeName, std::vector &vectorArray); + static bool getVector3DListAttribute(XmlNode &node, const char *attributeName, std::list &vectorList); + static bool getVector3DArrayAttribute(XmlNode &node, const char *attributeName, std::vector &vectorArray); + static bool getColor3DListAttribute(XmlNode &node, const char *attributeName, std::list &colorList); + static bool getColor4DListAttribute(XmlNode &node, const char *attributeName, std::list &colorList); +}; + +} // namespace Assimp diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 933b5488c..c99b81558 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -800,9 +800,23 @@ ADD_ASSIMP_IMPORTER( X ADD_ASSIMP_IMPORTER( X3D AssetLib/X3D/X3DImporter.cpp + AssetLib/X3D/X3DImporter_Geometry2D.cpp + AssetLib/X3D/X3DImporter_Geometry3D.cpp + AssetLib/X3D/X3DImporter_Group.cpp + AssetLib/X3D/X3DImporter_Light.cpp + AssetLib/X3D/X3DImporter_Metadata.cpp + AssetLib/X3D/X3DImporter_Networking.cpp + AssetLib/X3D/X3DImporter_Postprocess.cpp + AssetLib/X3D/X3DImporter_Rendering.cpp + AssetLib/X3D/X3DImporter_Shape.cpp + AssetLib/X3D/X3DImporter_Texturing.cpp AssetLib/X3D/X3DImporter.hpp + AssetLib/X3D/X3DImporter_Macro.hpp + AssetLib/X3D/X3DImporter_Node.hpp AssetLib/X3D/X3DGeoHelper.cpp AssetLib/X3D/X3DGeoHelper.h + AssetLib/X3D/X3DXmlHelper.cpp + AssetLib/X3D/X3DXmlHelper.h ) ADD_ASSIMP_IMPORTER( GLTF diff --git a/code/Common/ImporterRegistry.cpp b/code/Common/ImporterRegistry.cpp index 5df096166..7500d2610 100644 --- a/code/Common/ImporterRegistry.cpp +++ b/code/Common/ImporterRegistry.cpp @@ -365,9 +365,7 @@ void GetImporterInstanceList(std::vector &out) { out.push_back(new D3MFImporter()); #endif #ifndef ASSIMP_BUILD_NO_X3D_IMPORTER - if (devImportersEnabled) { // https://github.com/assimp/assimp/issues/3647 - out.push_back(new X3DImporter()); - } + out.push_back(new X3DImporter()); #endif #ifndef ASSIMP_BUILD_NO_MMD_IMPORTER out.push_back(new MMDImporter()); From a8a6aa2bd8b5f5eaf3d25e766ed1aebda6455c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Martin?= Date: Wed, 15 Sep 2021 14:49:46 +0200 Subject: [PATCH 08/28] fixed some compiling issues --- code/AssetLib/X3D/X3DImporter_Node.hpp | 2 +- code/AssetLib/X3D/X3DImporter_Postprocess.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/AssetLib/X3D/X3DImporter_Node.hpp b/code/AssetLib/X3D/X3DImporter_Node.hpp index 8079cbf11..95b8735d4 100644 --- a/code/AssetLib/X3D/X3DImporter_Node.hpp +++ b/code/AssetLib/X3D/X3DImporter_Node.hpp @@ -110,7 +110,7 @@ struct X3DNodeElementBase { protected: X3DNodeElementBase(X3DElemType type, X3DNodeElementBase *pParent) : - Type(type), Parent(pParent) { + Parent(pParent), Type(type) { // empty } }; diff --git a/code/AssetLib/X3D/X3DImporter_Postprocess.cpp b/code/AssetLib/X3D/X3DImporter_Postprocess.cpp index 24e03ca16..87121ef5f 100644 --- a/code/AssetLib/X3D/X3DImporter_Postprocess.cpp +++ b/code/AssetLib/X3D/X3DImporter_Postprocess.cpp @@ -703,7 +703,7 @@ void X3DImporter::Postprocess_CollectMetadata(const X3DNodeElementBase &pNodeEle // Add an element according to its type. if ((*it)->Type == X3DElemType::ENET_MetaBoolean) { if (((X3DNodeElementMetaBoolean *)cur_meta)->Value.size() > 0) - pSceneNode.mMetaData->Set(static_cast(meta_idx), cur_meta->Name, *(((X3DNodeElementMetaBoolean *)cur_meta)->Value.begin())); + pSceneNode.mMetaData->Set(static_cast(meta_idx), cur_meta->Name, *(((X3DNodeElementMetaBoolean *)cur_meta)->Value.begin()) == true); } else if ((*it)->Type == X3DElemType::ENET_MetaDouble) { if (((X3DNodeElementMetaDouble *)cur_meta)->Value.size() > 0) pSceneNode.mMetaData->Set(static_cast(meta_idx), cur_meta->Name, (float)*(((X3DNodeElementMetaDouble *)cur_meta)->Value.begin())); From 65173df9d9f0dfb86ae5a4ad2c7cfbaa6cd7b85a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Martin?= Date: Thu, 16 Sep 2021 13:45:56 +0200 Subject: [PATCH 09/28] fixed x3d importer test --- test/unit/utX3DImportExport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/utX3DImportExport.cpp b/test/unit/utX3DImportExport.cpp index 8d6390f39..de3bfbc96 100644 --- a/test/unit/utX3DImportExport.cpp +++ b/test/unit/utX3DImportExport.cpp @@ -52,7 +52,7 @@ public: virtual bool importerTest() { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/X3D/ComputerKeyboard.x3d", aiProcess_ValidateDataStructure); - return nullptr == scene; + return nullptr != scene; } }; From d0f720019677857e0f309f7b44be49ac2f3660b3 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Thu, 16 Sep 2021 21:01:44 +0200 Subject: [PATCH 10/28] Update X3DImporter_Geometry3D.cpp --- code/AssetLib/X3D/X3DImporter_Geometry3D.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/AssetLib/X3D/X3DImporter_Geometry3D.cpp b/code/AssetLib/X3D/X3DImporter_Geometry3D.cpp index 4250bfb00..53f5b27bb 100644 --- a/code/AssetLib/X3D/X3DImporter_Geometry3D.cpp +++ b/code/AssetLib/X3D/X3DImporter_Geometry3D.cpp @@ -812,7 +812,7 @@ void X3DImporter::readExtrusion(XmlNode &node) { { for(size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; cri++) { - ext_alias.Vertices.push_back(pointset_arr[spi][cri]); + ext_alias.Vertices.emplace_back(pointset_arr[spi][cri]); } } }// END: 4. Create vertices list. From e92177cb99babda293d070ff60b2d756a621f362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Martin?= Date: Fri, 17 Sep 2021 14:24:57 +0200 Subject: [PATCH 11/28] changed use and def attribute check for x3d importer to upper case This is how it is specified in the standard. --- code/AssetLib/X3D/X3DImporter_Macro.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/AssetLib/X3D/X3DImporter_Macro.hpp b/code/AssetLib/X3D/X3DImporter_Macro.hpp index 8d902b346..f493a86e9 100644 --- a/code/AssetLib/X3D/X3DImporter_Macro.hpp +++ b/code/AssetLib/X3D/X3DImporter_Macro.hpp @@ -69,8 +69,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \param [out] pUSE_Var - output variable name for "USE" value. #define MACRO_ATTRREAD_CHECKUSEDEF_RET(pNode, pDEF_Var, pUSE_Var) \ do { \ - XmlParser::getStdStrAttribute(pNode, "def", pDEF_Var); \ - XmlParser::getStdStrAttribute(pNode, "use", pUSE_Var); \ + XmlParser::getStdStrAttribute(pNode, "DEF", pDEF_Var); \ + XmlParser::getStdStrAttribute(pNode, "USE", pUSE_Var); \ } while (false) /// \def MACRO_FACE_ADD_QUAD_FA(pCCW, pOut, pIn, pP1, pP2, pP3, pP4) From 145f972d76eaf3cd947a92789ecefb4cc1b78765 Mon Sep 17 00:00:00 2001 From: Alex Rebert Date: Sat, 30 Oct 2021 13:43:41 -0400 Subject: [PATCH 12/28] Fix out-of-bounds read in RemoveLineComments Follow up to 6f07e89fdfb, which was not sufficient to fix the bug. Fix https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=24553 --- code/Common/RemoveComments.cpp | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/code/Common/RemoveComments.cpp b/code/Common/RemoveComments.cpp index 9974e985a..2de9666de 100644 --- a/code/Common/RemoveComments.cpp +++ b/code/Common/RemoveComments.cpp @@ -65,27 +65,19 @@ void CommentRemover::RemoveLineComments(const char* szComment, len = lenBuffer; } - char *szCurrent = szBuffer; - while (*szCurrent) { - + for(size_t i = 0; i < lenBuffer; i++) { // skip over quotes - if (*szCurrent == '\"' || *szCurrent == '\'') - while (*szCurrent++ && *szCurrent != '\"' && *szCurrent != '\''); + if (szBuffer[i] == '\"' || szBuffer[i] == '\'') + while (++i < lenBuffer && szBuffer[i] != '\"' && szBuffer[i] != '\''); - size_t lenRemaining = lenBuffer - (szCurrent - szBuffer); - if(lenRemaining < len) { + if(lenBuffer - i < len) { break; } - if (!strncmp(szCurrent,szComment,len)) { - while (!IsLineEnd(*szCurrent)) - *szCurrent++ = chReplacement; - - if (!*szCurrent) { - break; - } + if (!strncmp(szBuffer + i,szComment,len)) { + while (i < lenBuffer && !IsLineEnd(szBuffer[i])) + szBuffer[i++] = chReplacement; } - ++szCurrent; } } From 3b2a9d1543987354af2e88d7f83e0755a5fa726f Mon Sep 17 00:00:00 2001 From: kovacsv Date: Mon, 1 Nov 2021 08:09:16 +0100 Subject: [PATCH 13/28] Add assimpjs link. --- Readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Readme.md b/Readme.md index 949d60966..09f9af7fa 100644 --- a/Readme.md +++ b/Readme.md @@ -42,6 +42,7 @@ Take a look into the https://github.com/assimp/assimp/blob/master/Build.md file. * [.NET](https://bitbucket.org/Starnick/assimpnet/src/master/) * [Pascal](port/AssimpPascal/Readme.md) * [Javascript (Alpha)](https://github.com/makc/assimp2json) +* [Javascript/Node.js Interface](https://github.com/kovacsv/assimpjs) * [Unity 3d Plugin](https://ricardoreis.net/trilib-2/) * [JVM](https://github.com/kotlin-graphics/assimp) Full jvm port (current [status](https://github.com/kotlin-graphics/assimp/wiki/Status)) * [HAXE-Port](https://github.com/longde123/assimp-haxe) The Assimp-HAXE-port. From ee19ce60219ed6b7604b89c3af4b09bff35e84bd Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Mon, 8 Nov 2021 11:06:15 +0000 Subject: [PATCH 14/28] 3DS Export: Add support for aiShadingMode_PBR_BRDF Export as Phong. If no Diffuse texture, export the PBR base color instead, --- code/AssetLib/3DS/3DSExporter.cpp | 14 ++++++++++---- code/AssetLib/3DS/3DSExporter.h | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/code/AssetLib/3DS/3DSExporter.cpp b/code/AssetLib/3DS/3DSExporter.cpp index d1059ae43..ea92fa12c 100644 --- a/code/AssetLib/3DS/3DSExporter.cpp +++ b/code/AssetLib/3DS/3DSExporter.cpp @@ -330,6 +330,7 @@ void Discreet3DSExporter::WriteMaterials() { case aiShadingMode_Blinn: case aiShadingMode_CookTorrance: case aiShadingMode_Fresnel: + case aiShadingMode_PBR_BRDF: // Possibly should be Discreet3DS::Metal in some cases but this is undocumented shading_mode_out = Discreet3DS::Phong; break; @@ -356,7 +357,10 @@ void Discreet3DSExporter::WriteMaterials() { writer.PutI2(1); } - WriteTexture(mat, aiTextureType_DIFFUSE, Discreet3DS::CHUNK_MAT_TEXTURE); + // Fallback to BASE_COLOR if no DIFFUSE + if (!WriteTexture(mat, aiTextureType_DIFFUSE, Discreet3DS::CHUNK_MAT_TEXTURE)) + WriteTexture(mat, aiTextureType_BASE_COLOR, Discreet3DS::CHUNK_MAT_TEXTURE); + WriteTexture(mat, aiTextureType_HEIGHT, Discreet3DS::CHUNK_MAT_BUMPMAP); WriteTexture(mat, aiTextureType_OPACITY, Discreet3DS::CHUNK_MAT_OPACMAP); WriteTexture(mat, aiTextureType_SHININESS, Discreet3DS::CHUNK_MAT_MAT_SHINMAP); @@ -367,20 +371,21 @@ void Discreet3DSExporter::WriteMaterials() { } // ------------------------------------------------------------------------------------------------ -void Discreet3DSExporter::WriteTexture(const aiMaterial &mat, aiTextureType type, uint16_t chunk_flags) { +// returns true if the texture existed +bool Discreet3DSExporter::WriteTexture(const aiMaterial &mat, aiTextureType type, uint16_t chunk_flags) { aiString path; aiTextureMapMode map_mode[2] = { aiTextureMapMode_Wrap, aiTextureMapMode_Wrap }; ai_real blend = 1.0; if (mat.GetTexture(type, 0, &path, nullptr, nullptr, &blend, nullptr, map_mode) != AI_SUCCESS || !path.length) { - return; + return false; } // TODO: handle embedded textures properly if (path.data[0] == '*') { ASSIMP_LOG_ERROR("Ignoring embedded texture for export: ", path.C_Str()); - return; + return false; } ChunkWriter chunk(writer, chunk_flags); @@ -402,6 +407,7 @@ void Discreet3DSExporter::WriteTexture(const aiMaterial &mat, aiTextureType type writer.PutU2(val); } // TODO: export texture transformation (i.e. UV offset, scale, rotation) + return true; } // ------------------------------------------------------------------------------------------------ diff --git a/code/AssetLib/3DS/3DSExporter.h b/code/AssetLib/3DS/3DSExporter.h index c48ecb2fc..8ac3da98c 100644 --- a/code/AssetLib/3DS/3DSExporter.h +++ b/code/AssetLib/3DS/3DSExporter.h @@ -73,7 +73,7 @@ public: private: void WriteMeshes(); void WriteMaterials(); - void WriteTexture(const aiMaterial& mat, aiTextureType type, uint16_t chunk_flags); + bool WriteTexture(const aiMaterial& mat, aiTextureType type, uint16_t chunk_flags); void WriteFaceMaterialChunk(const aiMesh& mesh); int WriteHierarchy(const aiNode& node, int level, int sibling_level); void WriteString(const std::string& s); From 69cafe64b4c965a96c7a53d48d5870f90d6c9ffc Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Mon, 8 Nov 2021 14:41:13 +0000 Subject: [PATCH 15/28] Cap glTFv1 & 2 json size to ~4GB Ensures size_t cannot overflow Limits the maximum contiguous memory allocation to something plausible. --- code/AssetLib/glTF/glTFAsset.inl | 13 +++++++++++-- code/AssetLib/glTF2/glTF2Asset.inl | 17 +++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/code/AssetLib/glTF/glTFAsset.inl b/code/AssetLib/glTF/glTFAsset.inl index 387fd27df..353dd5aab 100644 --- a/code/AssetLib/glTF/glTFAsset.inl +++ b/code/AssetLib/glTF/glTFAsset.inl @@ -1148,7 +1148,7 @@ inline void Asset::ReadBinaryHeader(IOStream &stream) { AI_SWAP4(header.length); AI_SWAP4(header.sceneLength); - mSceneLength = static_cast(header.sceneLength); + mSceneLength = static_cast(header.sceneLength); // Can't be larger than 4GB (max. uint32_t) mBodyOffset = sizeof(header) + mSceneLength; mBodyOffset = (mBodyOffset + 3) & ~3; // Round up to next multiple of 4 @@ -1179,8 +1179,17 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) { mBodyLength = 0; } - // read the scene data + // Smallest legal JSON file is "{}" Smallest loadable glTF file is larger than that but catch it later + if (mSceneLength < 2) { + throw DeadlyImportError("GLTF: No JSON file contents"); + } + // Binary format only supports up to 4GB of JSON so limit it there to avoid extreme memory allocation + if (mSceneLength > std::numeric_limits::max()) { + throw DeadlyImportError("GLTF: JSON size greater than 4GB"); + } + + // read the scene data, ensure null termination std::vector sceneData(mSceneLength + 1); sceneData[mSceneLength] = '\0'; diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index d65b4132b..d9d783e47 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -1777,9 +1777,9 @@ inline void Asset::ReadBinaryHeader(IOStream &stream, std::vector &sceneDa throw DeadlyImportError("GLTF: JSON chunk missing"); } - // read the scene data + // read the scene data, ensure null termination - mSceneLength = chunk.chunkLength; + mSceneLength = chunk.chunkLength; // Can't be larger than 4GB (max. uint32_t) sceneData.resize(mSceneLength + 1); sceneData[mSceneLength] = '\0'; @@ -1835,9 +1835,13 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) { } else { mSceneLength = stream->FileSize(); mBodyLength = 0; + + // Binary format only supports up to 4GB of JSON, use that as a maximum + if (mSceneLength > std::numeric_limits::max()) { + throw DeadlyImportError("GLTF: JSON size greater than 4GB"); + } - // read the scene data - + // read the scene data, ensure null termination sceneData.resize(mSceneLength + 1); sceneData[mSceneLength] = '\0'; @@ -1846,6 +1850,11 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) { } } + // Smallest legal JSON file is "{}" Smallest loadable glTF file is larger than that but catch it later + if (mSceneLength < 2) { + throw DeadlyImportError("GLTF: No JSON file contents"); + } + // parse the JSON document ASSIMP_LOG_DEBUG("Parsing GLTF2 JSON"); Document doc; From 9433fc526a775bb290d477f92add5189fc3682eb Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Mon, 8 Nov 2021 14:42:21 +0000 Subject: [PATCH 16/28] Apply clangformat --- code/AssetLib/glTF/glTFAsset.inl | 103 ++++++++++++++-------------- code/AssetLib/glTF2/glTF2Asset.inl | 105 ++++++++++++++--------------- 2 files changed, 103 insertions(+), 105 deletions(-) diff --git a/code/AssetLib/glTF/glTFAsset.inl b/code/AssetLib/glTF/glTFAsset.inl index 353dd5aab..896a9f16c 100644 --- a/code/AssetLib/glTF/glTFAsset.inl +++ b/code/AssetLib/glTF/glTFAsset.inl @@ -39,8 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -#include #include +#include #include // Header files, Assimp @@ -57,11 +57,10 @@ using namespace glTFCommon; namespace glTF { #if _MSC_VER -# pragma warning(push) -# pragma warning(disable : 4706) +#pragma warning(push) +#pragma warning(disable : 4706) #endif // _MSC_VER - // // LazyDict methods // @@ -214,9 +213,10 @@ inline void Buffer::Read(Value &obj, Asset &r) { } else { // Local file if (byteLength > 0) { std::string dir = !r.mCurrentAssetDir.empty() ? ( - r.mCurrentAssetDir.back() == '/' ? - r.mCurrentAssetDir : r.mCurrentAssetDir + '/' - ) : ""; + r.mCurrentAssetDir.back() == '/' ? + r.mCurrentAssetDir : + r.mCurrentAssetDir + '/') : + ""; IOStream *file = r.OpenFile(dir + uri, "rb"); if (file) { @@ -734,8 +734,8 @@ inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) { ASSIMP_LOG_INFO("GLTF: Decompressing Open3DGC data."); /************** Read data from JSON-document **************/ -#define MESH_READ_COMPRESSEDDATA_MEMBER(pFieldName, pOut) \ - if (!ReadMember(*comp_data, pFieldName, pOut)) { \ +#define MESH_READ_COMPRESSEDDATA_MEMBER(pFieldName, pOut) \ + if (!ReadMember(*comp_data, pFieldName, pOut)) { \ throw DeadlyImportError("GLTF: \"compressedData\" must has \"", pFieldName, "\"."); \ } @@ -771,8 +771,7 @@ inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) { Decode_O3DGC(*ext_o3dgc, pAsset_Root); Extension.push_back(ext_o3dgc); // store info in mesh extensions list. } // if(it_memb->name.GetString() == "Open3DGC-compression") - else - { + else { throw DeadlyImportError("GLTF: Unknown mesh extension: \"", it_memb->name.GetString(), "\"."); } } // for(Value::MemberIterator it_memb = json_extensions->MemberBegin(); it_memb != json_extensions->MemberEnd(); json_extensions++) @@ -842,21 +841,21 @@ inline void Mesh::Decode_O3DGC(const SCompression_Open3DGC &pCompression_Open3DG size_t tval = ifs.GetNFloatAttribute(static_cast(idx)); switch (ifs.GetFloatAttributeType(static_cast(idx))) { - case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD: - // Check situation when encoded data contain texture coordinates but primitive not. - if (idx_texcoord < primitives[0].attributes.texcoord.size()) { - if (primitives[0].attributes.texcoord[idx]->count != tval) - throw DeadlyImportError("GLTF: Open3DGC. Compressed texture coordinates count (", ai_to_string(tval), - ") not equal to uncompressed (", ai_to_string(primitives[0].attributes.texcoord[idx]->count), ")."); + case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD: + // Check situation when encoded data contain texture coordinates but primitive not. + if (idx_texcoord < primitives[0].attributes.texcoord.size()) { + if (primitives[0].attributes.texcoord[idx]->count != tval) + throw DeadlyImportError("GLTF: Open3DGC. Compressed texture coordinates count (", ai_to_string(tval), + ") not equal to uncompressed (", ai_to_string(primitives[0].attributes.texcoord[idx]->count), ")."); - idx_texcoord++; - } else { - ifs.SetNFloatAttribute(static_cast(idx), 0ul); // Disable decoding this attribute. - } + idx_texcoord++; + } else { + ifs.SetNFloatAttribute(static_cast(idx), 0ul); // Disable decoding this attribute. + } - break; - default: - throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: ", ai_to_string(ifs.GetFloatAttributeType(static_cast(idx)))); + break; + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: ", ai_to_string(ifs.GetFloatAttributeType(static_cast(idx)))); } tval *= ifs.GetFloatAttributeDim(static_cast(idx)) * sizeof(o3dgc::Real); // After checking count of objects we can get size of array. @@ -868,14 +867,14 @@ inline void Mesh::Decode_O3DGC(const SCompression_Open3DGC &pCompression_Open3DG // size = number_of_elements * components_per_element * size_of_component. See float attributes note. size_t tval = ifs.GetNIntAttribute(static_cast(idx)); switch (ifs.GetIntAttributeType(static_cast(idx))) { - case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN: - case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX: - case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID: - case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID: - break; + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID: + break; - default: - throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: ", ai_to_string(ifs.GetIntAttributeType(static_cast(idx)))); + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: ", ai_to_string(ifs.GetIntAttributeType(static_cast(idx)))); } tval *= ifs.GetIntAttributeDim(static_cast(idx)) * sizeof(long); // See float attributes note. @@ -901,30 +900,30 @@ inline void Mesh::Decode_O3DGC(const SCompression_Open3DGC &pCompression_Open3DG for (size_t idx = 0, idx_end = size_floatattr.size(), idx_texcoord = 0; idx < idx_end; idx++) { switch (ifs.GetFloatAttributeType(static_cast(idx))) { - case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD: - if (idx_texcoord < primitives[0].attributes.texcoord.size()) { - // See above about absent attributes. - ifs.SetFloatAttribute(static_cast(idx), (o3dgc::Real *const)(decoded_data + get_buf_offset(primitives[0].attributes.texcoord[idx]))); - idx_texcoord++; - } + case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD: + if (idx_texcoord < primitives[0].attributes.texcoord.size()) { + // See above about absent attributes. + ifs.SetFloatAttribute(static_cast(idx), (o3dgc::Real *const)(decoded_data + get_buf_offset(primitives[0].attributes.texcoord[idx]))); + idx_texcoord++; + } - break; - default: - throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: ", ai_to_string(ifs.GetFloatAttributeType(static_cast(idx)))); + break; + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: ", ai_to_string(ifs.GetFloatAttributeType(static_cast(idx)))); } } for (size_t idx = 0, idx_end = size_intattr.size(); idx < idx_end; idx++) { switch (ifs.GetIntAttributeType(static_cast(idx))) { - case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN: - case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX: - case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID: - case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID: - break; + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID: + case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID: + break; - // ifs.SetIntAttribute(idx, (long* const)(decoded_data + get_buf_offset(primitives[0].attributes.joint))); - default: - throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: ", ai_to_string(ifs.GetIntAttributeType(static_cast(idx)))); + // ifs.SetIntAttribute(idx, (long* const)(decoded_data + get_buf_offset(primitives[0].attributes.joint))); + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: ", ai_to_string(ifs.GetIntAttributeType(static_cast(idx)))); } } @@ -1181,12 +1180,12 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) { // Smallest legal JSON file is "{}" Smallest loadable glTF file is larger than that but catch it later if (mSceneLength < 2) { - throw DeadlyImportError("GLTF: No JSON file contents"); + throw DeadlyImportError("GLTF: No JSON file contents"); } // Binary format only supports up to 4GB of JSON so limit it there to avoid extreme memory allocation if (mSceneLength > std::numeric_limits::max()) { - throw DeadlyImportError("GLTF: JSON size greater than 4GB"); + throw DeadlyImportError("GLTF: JSON size greater than 4GB"); } // read the scene data, ensure null termination @@ -1267,7 +1266,7 @@ inline void Asset::ReadExtensionsUsed(Document &doc) { #undef CHECK_EXT } -inline IOStream *Asset::OpenFile(const std::string& path, const char *mode, bool absolute) { +inline IOStream *Asset::OpenFile(const std::string &path, const char *mode, bool absolute) { #ifdef ASSIMP_API (void)absolute; return mIOSystem->Open(path, mode); @@ -1309,7 +1308,7 @@ inline std::string Asset::FindUniqueID(const std::string &str, const char *suffi } #if _MSC_VER -# pragma warning(pop) +#pragma warning(pop) #endif // _MSC_VER } // namespace glTF diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index d9d783e47..479749523 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -197,18 +197,18 @@ inline void SetDecodedIndexBuffer_Draco(const draco::Mesh &dracoMesh, Mesh::Prim // Not same size, convert switch (componentBytes) { - case sizeof(uint32_t): - CopyFaceIndex_Draco(*decodedIndexBuffer, dracoMesh); - break; - case sizeof(uint16_t): - CopyFaceIndex_Draco(*decodedIndexBuffer, dracoMesh); - break; - case sizeof(uint8_t): - CopyFaceIndex_Draco(*decodedIndexBuffer, dracoMesh); - break; - default: - ai_assert(false); - break; + case sizeof(uint32_t): + CopyFaceIndex_Draco(*decodedIndexBuffer, dracoMesh); + break; + case sizeof(uint16_t): + CopyFaceIndex_Draco(*decodedIndexBuffer, dracoMesh); + break; + case sizeof(uint8_t): + CopyFaceIndex_Draco(*decodedIndexBuffer, dracoMesh); + break; + default: + ai_assert(false); + break; } // Assign this alternate data buffer to the accessor @@ -247,27 +247,27 @@ inline void SetDecodedAttributeBuffer_Draco(const draco::Mesh &dracoMesh, uint32 decodedAttribBuffer->Grow(dracoMesh.num_points() * pDracoAttribute->num_components() * componentBytes); switch (accessor.componentType) { - case ComponentType_BYTE: - GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); - break; - case ComponentType_UNSIGNED_BYTE: - GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); - break; - case ComponentType_SHORT: - GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); - break; - case ComponentType_UNSIGNED_SHORT: - GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); - break; - case ComponentType_UNSIGNED_INT: - GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); - break; - case ComponentType_FLOAT: - GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); - break; - default: - ai_assert(false); - break; + case ComponentType_BYTE: + GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); + break; + case ComponentType_UNSIGNED_BYTE: + GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); + break; + case ComponentType_SHORT: + GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); + break; + case ComponentType_UNSIGNED_SHORT: + GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); + break; + case ComponentType_UNSIGNED_INT: + GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); + break; + case ComponentType_FLOAT: + GetAttributeForAllPoints_Draco(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); + break; + default: + ai_assert(false); + break; } // Assign this alternate data buffer to the accessor @@ -299,7 +299,7 @@ inline LazyDict::~LazyDict() { template inline void LazyDict::AttachToDocument(Document &doc) { Value *container = nullptr; - const char* context = nullptr; + const char *context = nullptr; if (mExtId) { if (Value *exts = FindObject(doc, "extensions")) { @@ -721,18 +721,18 @@ inline void Accessor::Sparse::PatchData(unsigned int elementSize) { while (pIndices != indicesEnd) { size_t offset; switch (indicesType) { - case ComponentType_UNSIGNED_BYTE: - offset = *pIndices; - break; - case ComponentType_UNSIGNED_SHORT: - offset = *reinterpret_cast(pIndices); - break; - case ComponentType_UNSIGNED_INT: - offset = *reinterpret_cast(pIndices); - break; - default: - // have fun with float and negative values from signed types as indices. - throw DeadlyImportError("Unsupported component type in index."); + case ComponentType_UNSIGNED_BYTE: + offset = *pIndices; + break; + case ComponentType_UNSIGNED_SHORT: + offset = *reinterpret_cast(pIndices); + break; + case ComponentType_UNSIGNED_INT: + offset = *reinterpret_cast(pIndices); + break; + default: + // have fun with float and negative values from signed types as indices. + throw DeadlyImportError("Unsupported component type in index."); } offset *= elementSize; @@ -751,9 +751,8 @@ inline void Accessor::Read(Value &obj, Asset &r) { byteOffset = MemberOrDefault(obj, "byteOffset", size_t(0)); componentType = MemberOrDefault(obj, "componentType", ComponentType_BYTE); { - const Value* countValue = FindUInt(obj, "count"); - if (!countValue) - { + const Value *countValue = FindUInt(obj, "count"); + if (!countValue) { throw DeadlyImportError("A count value is required, when reading ", id.c_str(), name.empty() ? "" : " (" + name + ")"); } count = countValue->GetUint(); @@ -1233,7 +1232,7 @@ inline void Material::Read(Value &material, Asset &r) { MaterialIOR ior; ReadMember(*curMaterialIOR, "ior", ior.ior); - + this->materialIOR = Nullable(ior); } } @@ -1835,7 +1834,7 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) { } else { mSceneLength = stream->FileSize(); mBodyLength = 0; - + // Binary format only supports up to 4GB of JSON, use that as a maximum if (mSceneLength > std::numeric_limits::max()) { throw DeadlyImportError("GLTF: JSON size greater than 4GB"); @@ -1852,7 +1851,7 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) { // Smallest legal JSON file is "{}" Smallest loadable glTF file is larger than that but catch it later if (mSceneLength < 2) { - throw DeadlyImportError("GLTF: No JSON file contents"); + throw DeadlyImportError("GLTF: No JSON file contents"); } // parse the JSON document @@ -1983,7 +1982,7 @@ inline void Asset::ReadExtensionsUsed(Document &doc) { #undef CHECK_EXT } -inline IOStream *Asset::OpenFile(const std::string& path, const char *mode, bool /*absolute*/) { +inline IOStream *Asset::OpenFile(const std::string &path, const char *mode, bool /*absolute*/) { #ifdef ASSIMP_API return mIOSystem->Open(path, mode); #else From aaae3e3a10902cd3c3eec18e18233ac6b6f77185 Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Mon, 8 Nov 2021 15:05:20 +0000 Subject: [PATCH 17/28] size_t is 32bit on some platforms Also assert if size_t is smaller than uint32_t (probably not necessary) Note: 32bit builds will crash OOM if a really large model is loaded, as cannot allocate that much in total, let alone contiguously. --- code/AssetLib/glTF/glTFAsset.inl | 3 ++- code/AssetLib/glTF2/glTF2Asset.inl | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/code/AssetLib/glTF/glTFAsset.inl b/code/AssetLib/glTF/glTFAsset.inl index 896a9f16c..10be7c78e 100644 --- a/code/AssetLib/glTF/glTFAsset.inl +++ b/code/AssetLib/glTF/glTFAsset.inl @@ -1147,6 +1147,7 @@ inline void Asset::ReadBinaryHeader(IOStream &stream) { AI_SWAP4(header.length); AI_SWAP4(header.sceneLength); + static_assert(std::numeric_limits::max() <= std::numeric_limits::max(), "size_t must be at least 32bits"); mSceneLength = static_cast(header.sceneLength); // Can't be larger than 4GB (max. uint32_t) mBodyOffset = sizeof(header) + mSceneLength; @@ -1184,7 +1185,7 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) { } // Binary format only supports up to 4GB of JSON so limit it there to avoid extreme memory allocation - if (mSceneLength > std::numeric_limits::max()) { + if (mSceneLength >= std::numeric_limits::max()) { throw DeadlyImportError("GLTF: JSON size greater than 4GB"); } diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index 479749523..3fc238208 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -1777,7 +1777,7 @@ inline void Asset::ReadBinaryHeader(IOStream &stream, std::vector &sceneDa } // read the scene data, ensure null termination - + static_assert(std::numeric_limits::max() <= std::numeric_limits::max(), "size_t must be at least 32bits"); mSceneLength = chunk.chunkLength; // Can't be larger than 4GB (max. uint32_t) sceneData.resize(mSceneLength + 1); sceneData[mSceneLength] = '\0'; @@ -1836,7 +1836,7 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) { mBodyLength = 0; // Binary format only supports up to 4GB of JSON, use that as a maximum - if (mSceneLength > std::numeric_limits::max()) { + if (mSceneLength >= std::numeric_limits::max()) { throw DeadlyImportError("GLTF: JSON size greater than 4GB"); } From 86a25b62e4ba019ba279d95f92e8c607d05048e1 Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Tue, 9 Nov 2021 13:03:06 +0000 Subject: [PATCH 18/28] Better aiMesh ABI compatibility with 5.0.1, make smaller Move new mTextureCoordsNames member to end of struct Convert to pointer-to-array, saving ~8KB per aiMesh in almost all cases Add C++ accessor functions for simpler usage --- code/AssetLib/Assxml/AssxmlFileWriter.cpp | 2 +- code/AssetLib/FBX/FBXConverter.cpp | 2 +- include/assimp/mesh.h | 65 +++++++++++++-- test/CMakeLists.txt | 1 + test/unit/Common/utMesh.cpp | 96 +++++++++++++++++++++++ 5 files changed, 159 insertions(+), 7 deletions(-) create mode 100644 test/unit/Common/utMesh.cpp diff --git a/code/AssetLib/Assxml/AssxmlFileWriter.cpp b/code/AssetLib/Assxml/AssxmlFileWriter.cpp index d16580a2e..80c2db908 100644 --- a/code/AssetLib/Assxml/AssxmlFileWriter.cpp +++ b/code/AssetLib/Assxml/AssxmlFileWriter.cpp @@ -601,7 +601,7 @@ static void WriteDump(const char *pFile, const char *cmd, const aiScene *scene, ioprintf(io, "\t\t \n", mesh->mNumVertices, a, - mesh->mTextureCoordsNames[a].C_Str(), + (mesh->HasTextureCoordsName(a) ? mesh->GetTextureCoordsName(a)->C_Str() : ""), mesh->mNumUVComponents[a]); if (!shortened) { diff --git a/code/AssetLib/FBX/FBXConverter.cpp b/code/AssetLib/FBX/FBXConverter.cpp index c025e8954..d4500fb31 100644 --- a/code/AssetLib/FBX/FBXConverter.cpp +++ b/code/AssetLib/FBX/FBXConverter.cpp @@ -1128,7 +1128,7 @@ unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, c *out_uv++ = aiVector3D(v.x, v.y, 0.0f); } - out_mesh->mTextureCoordsNames[i] = mesh.GetTextureCoordChannelName(i); + out_mesh->SetTextureCoordsName(i, aiString(mesh.GetTextureCoordChannelName(i))); out_mesh->mNumUVComponents[i] = 2; } diff --git a/include/assimp/mesh.h b/include/assimp/mesh.h index 8223b3443..c755c591a 100644 --- a/include/assimp/mesh.h +++ b/include/assimp/mesh.h @@ -673,10 +673,6 @@ struct aiMesh { */ C_STRUCT aiVector3D *mTextureCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS]; - /** Vertex stream names. - */ - C_STRUCT aiString mTextureCoordsNames[AI_MAX_NUMBER_OF_TEXTURECOORDS]; - /** Specifies the number of components for a given UV channel. * Up to three channels are supported (UVW, for accessing volume * or cube maps). If the value is 2 for a given channel n, the @@ -744,6 +740,10 @@ struct aiMesh { */ C_STRUCT aiAABB mAABB; + /** Vertex UV stream names. Pointer to array of size AI_MAX_NUMBER_OF_TEXTURECOORDS + */ + C_STRUCT aiString **mTextureCoordsNames; + #ifdef __cplusplus //! Default constructor. Initializes all members to 0 @@ -765,7 +765,8 @@ struct aiMesh { mNumAnimMeshes(0), mAnimMeshes(nullptr), mMethod(0), - mAABB() { + mAABB(), + mTextureCoordsNames(nullptr) { for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) { mNumUVComponents[a] = 0; mTextureCoords[a] = nullptr; @@ -785,6 +786,14 @@ struct aiMesh { for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; a++) { delete[] mTextureCoords[a]; } + + if (mTextureCoordsNames) { + for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; a++) { + delete mTextureCoordsNames[a]; + } + delete[] mTextureCoordsNames; + } + for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; a++) { delete[] mColors[a]; } @@ -870,6 +879,52 @@ struct aiMesh { return mBones != nullptr && mNumBones > 0; } + //! Check whether the mesh contains a texture coordinate set name + //! \param pIndex Index of the texture coordinates set + bool HasTextureCoordsName(unsigned int pIndex) const { + if (mTextureCoordsNames == nullptr || pIndex >= AI_MAX_NUMBER_OF_TEXTURECOORDS) { + return false; + } + return mTextureCoordsNames[pIndex] != nullptr; + } + + //! Set a texture coordinate set name + //! \param pIndex Index of the texture coordinates set + //! \param texCoordsName name of the texture coordinate set + void SetTextureCoordsName(unsigned int pIndex, const aiString &texCoordsName) { + if (pIndex >= AI_MAX_NUMBER_OF_TEXTURECOORDS) { + return; + } + + if (mTextureCoordsNames == nullptr) { + // Construct and null-init array + mTextureCoordsNames = new aiString *[AI_MAX_NUMBER_OF_TEXTURECOORDS] {}; + } + + if (texCoordsName.length == 0) { + delete mTextureCoordsNames[pIndex]; + mTextureCoordsNames[pIndex] = nullptr; + return; + } + + if (mTextureCoordsNames[pIndex] == nullptr) { + mTextureCoordsNames[pIndex] = new aiString(texCoordsName); + return; + } + + *mTextureCoordsNames[pIndex] = texCoordsName; + } + + //! Get a texture coordinate set name + //! \param pIndex Index of the texture coordinates set + const aiString *GetTextureCoordsName(unsigned int pIndex) const { + if (mTextureCoordsNames == nullptr || pIndex >= AI_MAX_NUMBER_OF_TEXTURECOORDS) { + return nullptr; + } + + return mTextureCoordsNames[pIndex]; + } + #endif // __cplusplus }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6f28d4183..5a0e2e92d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -90,6 +90,7 @@ SET( COMMON unit/utProfiler.cpp unit/utSharedPPData.cpp unit/utStringUtils.cpp + unit/Common/utMesh.cpp unit/Common/utStandardShapes.cpp unit/Common/uiScene.cpp unit/Common/utLineSplitter.cpp diff --git a/test/unit/Common/utMesh.cpp b/test/unit/Common/utMesh.cpp new file mode 100644 index 000000000..da0c1084d --- /dev/null +++ b/test/unit/Common/utMesh.cpp @@ -0,0 +1,96 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2021, 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. +--------------------------------------------------------------------------- +*/ + +#include "UnitTestPCH.h" + +#include + +using namespace Assimp; + +class utMesh : public ::testing::Test { +protected: + aiMesh* mesh = nullptr; + + void SetUp() override { + mesh = new aiMesh; + } + + void TearDown() override { + delete mesh; + mesh = nullptr; + } +}; + +TEST_F(utMesh, emptyMeshHasNoContentTest) { + EXPECT_EQ(0, mesh->mName.length); + EXPECT_FALSE(mesh->HasPositions()); + EXPECT_FALSE(mesh->HasFaces()); + EXPECT_FALSE(mesh->HasNormals()); + EXPECT_FALSE(mesh->HasTangentsAndBitangents()); + EXPECT_FALSE(mesh->HasVertexColors(0)); + EXPECT_FALSE(mesh->HasVertexColors(AI_MAX_NUMBER_OF_COLOR_SETS)); + EXPECT_FALSE(mesh->HasTextureCoords(0)); + EXPECT_FALSE(mesh->HasTextureCoords(AI_MAX_NUMBER_OF_TEXTURECOORDS)); + EXPECT_EQ(0, mesh->GetNumUVChannels()); + EXPECT_EQ(0, mesh->GetNumColorChannels()); + EXPECT_FALSE(mesh->HasBones()); + EXPECT_FALSE(mesh->HasTextureCoordsName(0)); + EXPECT_FALSE(mesh->HasTextureCoordsName(AI_MAX_NUMBER_OF_TEXTURECOORDS)); +} + +TEST_F(utMesh, setTextureCoordsName) { + EXPECT_FALSE(mesh->HasTextureCoordsName(0)); + const aiString texcoords_name("texcoord_name"); + mesh->SetTextureCoordsName(0, texcoords_name); + EXPECT_TRUE(mesh->HasTextureCoordsName(0)); + EXPECT_FALSE(mesh->HasTextureCoordsName(1)); + ASSERT_NE(nullptr, mesh->mTextureCoordsNames); + ASSERT_NE(nullptr, mesh->mTextureCoordsNames[0]); + EXPECT_STREQ(texcoords_name.C_Str(), mesh->mTextureCoordsNames[0]->C_Str()); + EXPECT_STREQ(texcoords_name.C_Str(), mesh->GetTextureCoordsName(0)->C_Str()); + + // Now clear the name + mesh->SetTextureCoordsName(0, aiString()); + EXPECT_FALSE(mesh->HasTextureCoordsName(0)); + ASSERT_NE(nullptr, mesh->mTextureCoordsNames); + EXPECT_EQ(nullptr, mesh->mTextureCoordsNames[0]); + EXPECT_EQ(nullptr, mesh->GetTextureCoordsName(0)); +} From b8a10e62f9ba1d69b52a23e9a95e5fb2baed5e0d Mon Sep 17 00:00:00 2001 From: Aaron Gokaslan Date: Tue, 9 Nov 2021 13:08:13 -0500 Subject: [PATCH 19/28] Remove optimization fence --- code/AssetLib/STEPParser/STEPFileReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/AssetLib/STEPParser/STEPFileReader.cpp b/code/AssetLib/STEPParser/STEPFileReader.cpp index 360277912..09a596aa4 100644 --- a/code/AssetLib/STEPParser/STEPFileReader.cpp +++ b/code/AssetLib/STEPParser/STEPFileReader.cpp @@ -325,7 +325,7 @@ std::shared_ptr EXPRESS::DataType::Parse(const char*& i std::transform(s.begin(),s.end(),s.begin(),&ai_tolower ); if (schema->IsKnownToken(s)) { for(cur = t+1;*cur++ != '(';); - const std::shared_ptr dt = Parse(cur); + std::shared_ptr dt = Parse(cur); inout = *cur ? cur+1 : cur; return dt; } From 7ddd9b2484b863b78602d98b68586243a68fd02e Mon Sep 17 00:00:00 2001 From: iraj mohtasham <36444724+irajsb@users.noreply.github.com> Date: Wed, 10 Nov 2021 00:04:12 +0330 Subject: [PATCH 20/28] Added UE4 plugin --- Readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Readme.md b/Readme.md index 949d60966..4196bba76 100644 --- a/Readme.md +++ b/Readme.md @@ -43,6 +43,7 @@ Take a look into the https://github.com/assimp/assimp/blob/master/Build.md file. * [Pascal](port/AssimpPascal/Readme.md) * [Javascript (Alpha)](https://github.com/makc/assimp2json) * [Unity 3d Plugin](https://ricardoreis.net/trilib-2/) +* [Unreal Engine Plugin](https://github.com/irajsb/UE4_Assimp/) * [JVM](https://github.com/kotlin-graphics/assimp) Full jvm port (current [status](https://github.com/kotlin-graphics/assimp/wiki/Status)) * [HAXE-Port](https://github.com/longde123/assimp-haxe) The Assimp-HAXE-port. * [Rust](https://github.com/jkvargas/russimp) From e206d699da026aa88dbeb43730c0818f428a03f7 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 9 Nov 2021 23:26:20 +0100 Subject: [PATCH 21/28] Change version to 5.1.0 - Change version for Version 5.1.0-RC1 to 5.1.0 - closes https://github.com/assimp/assimp/issues/4161 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f893646a..4e47b5732 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,7 +53,7 @@ IF(ASSIMP_HUNTER_ENABLED) add_definitions(-DASSIMP_USE_HUNTER) ENDIF() -PROJECT( Assimp VERSION 5.0.1 ) +PROJECT(Assimp VERSION 5.1.0) # All supported options ############################################### From 8a613a86555008a8310a3d0a62e51e2335cf721e Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 9 Nov 2021 23:27:58 +0100 Subject: [PATCH 22/28] Fix unittest --- test/unit/utVersion.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/utVersion.cpp b/test/unit/utVersion.cpp index 0de6ef39c..6577c7758 100644 --- a/test/unit/utVersion.cpp +++ b/test/unit/utVersion.cpp @@ -44,16 +44,16 @@ class utVersion : public ::testing::Test { }; TEST_F( utVersion, aiGetLegalStringTest ) { - const char *lv( aiGetLegalString() ); + const char *lv = aiGetLegalString(); EXPECT_NE( lv, nullptr ); std::string text( lv ); - size_t pos( text.find( std::string( "2021" ) ) ); + size_t pos = text.find(std::string("2021")); EXPECT_NE( pos, std::string::npos ); } TEST_F( utVersion, aiGetVersionMinorTest ) { - EXPECT_EQ( aiGetVersionMinor(), 0U ); + EXPECT_EQ( aiGetVersionMinor(), 1U ); } TEST_F( utVersion, aiGetVersionMajorTest ) { From c01d33a77a388be98ab4316979949d0294c8a619 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Wed, 10 Nov 2021 09:12:35 +0100 Subject: [PATCH 23/28] Update anim.h - Add missing whitespaces --- include/assimp/anim.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/assimp/anim.h b/include/assimp/anim.h index dcd054d1e..8a73297a5 100644 --- a/include/assimp/anim.h +++ b/include/assimp/anim.h @@ -98,6 +98,7 @@ struct aiVectorKey { bool operator<(const aiVectorKey &rhs) const { return mTime < rhs.mTime; } + bool operator>(const aiVectorKey &rhs) const { return mTime > rhs.mTime; } @@ -131,6 +132,7 @@ struct aiQuatKey { bool operator==(const aiQuatKey &rhs) const { return rhs.mValue == this->mValue; } + bool operator!=(const aiQuatKey &rhs) const { return rhs.mValue != this->mValue; } @@ -139,6 +141,7 @@ struct aiQuatKey { bool operator<(const aiQuatKey &rhs) const { return mTime < rhs.mTime; } + bool operator>(const aiQuatKey &rhs) const { return mTime > rhs.mTime; } From 5fd2a5559cda5b8d821e25d0bfeb68aba83a3007 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Wed, 10 Nov 2021 09:19:29 +0100 Subject: [PATCH 24/28] Update mesh.h - Add initialization for armature attributes. - closes https://github.com/assimp/assimp/issues/4158 --- include/assimp/mesh.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/assimp/mesh.h b/include/assimp/mesh.h index 8223b3443..225f8556a 100644 --- a/include/assimp/mesh.h +++ b/include/assimp/mesh.h @@ -300,6 +300,10 @@ struct aiBone { aiBone() AI_NO_EXCEPT : mName(), mNumWeights(0), +#ifndef ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS + mArmature(nullptr), + mNode(nullptr), +#endif mWeights(nullptr), mOffsetMatrix() { // empty @@ -309,6 +313,10 @@ struct aiBone { aiBone(const aiBone &other) : mName(other.mName), mNumWeights(other.mNumWeights), +#ifndef ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS + mArmature(nullptr), + mNode(nullptr), +#endif mWeights(nullptr), mOffsetMatrix(other.mOffsetMatrix) { if (other.mWeights && other.mNumWeights) { From 8ed18621dbb7d906ddbb79a366d1f987f826c5b4 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Wed, 10 Nov 2021 20:37:27 +0100 Subject: [PATCH 25/28] Fixes --- code/AssetLib/X3D/X3DImporter.cpp | 12 +- code/AssetLib/X3D/X3DImporter.hpp | 1 + code/AssetLib/X3D/X3DImporter_Geometry2D.cpp | 16 +- code/AssetLib/X3D/X3DImporter_Geometry3D.cpp | 1102 +++++++++--------- code/AssetLib/X3D/X3DImporter_Group.cpp | 15 +- code/AssetLib/X3D/X3DImporter_Light.cpp | 6 +- code/AssetLib/X3D/X3DImporter_Macro.hpp | 32 +- code/AssetLib/X3D/X3DImporter_Metadata.cpp | 6 +- code/AssetLib/X3D/X3DImporter_Networking.cpp | 5 +- code/AssetLib/X3D/X3DImporter_Rendering.cpp | 40 +- code/AssetLib/X3D/X3DImporter_Shape.cpp | 158 +-- code/AssetLib/X3D/X3DImporter_Texturing.cpp | 6 +- test/models/X3D/HelloX3dTrademark.x3d | 48 + test/test.3mf | Bin 1143 -> 1143 bytes test/unit/utX3DImportExport.cpp | 2 +- 15 files changed, 741 insertions(+), 708 deletions(-) create mode 100644 test/models/X3D/HelloX3dTrademark.x3d diff --git a/code/AssetLib/X3D/X3DImporter.cpp b/code/AssetLib/X3D/X3DImporter.cpp index bfe83a49f..e33782785 100644 --- a/code/AssetLib/X3D/X3DImporter.cpp +++ b/code/AssetLib/X3D/X3DImporter.cpp @@ -75,7 +75,7 @@ bool X3DImporter::isNodeEmpty(XmlNode &node) { } void X3DImporter::checkNodeMustBeEmpty(XmlNode &node) { - if (isNodeEmpty(node)) throw DeadlyImportError(std::string("Node <") + node.name() + "> must be empty."); + if (!isNodeEmpty(node)) throw DeadlyImportError(std::string("Node <") + node.name() + "> must be empty."); } void X3DImporter::skipUnsupportedNode(const std::string &pParentNodeName, XmlNode &node) { @@ -321,7 +321,7 @@ void X3DImporter::readHead(XmlNode &node) { for (auto currentNode : node.children()) { const std::string ¤tName = currentNode.name(); if (currentName == "meta") { - checkNodeMustBeEmpty(node); + //checkNodeMustBeEmpty(node); meta_entry entry; if (XmlParser::getStdStrAttribute(currentNode, "name", entry.name)) { XmlParser::getStdStrAttribute(currentNode, "content", entry.value); @@ -339,6 +339,9 @@ void X3DImporter::readHead(XmlNode &node) { } void X3DImporter::readChildNodes(XmlNode &node, const std::string &pParentNodeName) { + if (node.empty()) { + return; + } for (auto currentNode : node.children()) { const std::string ¤tName = currentNode.name(); if (currentName == "Shape") @@ -463,6 +466,8 @@ void X3DImporter::ParseHelper_Group_Begin(const bool pStatic) { } void X3DImporter::ParseHelper_Node_Enter(X3DNodeElementBase *pNode) { + ai_assert(nullptr != pNode); + mNodeElementCur->Children.push_back(pNode); // add new element to current element child list. mNodeElementCur = pNode; // switch current element to new one. } @@ -471,6 +476,9 @@ void X3DImporter::ParseHelper_Node_Exit() { // check if we can walk up. if (mNodeElementCur != nullptr) { mNodeElementCur = mNodeElementCur->Parent; + } else { + int i = 0; + ++i; } } diff --git a/code/AssetLib/X3D/X3DImporter.hpp b/code/AssetLib/X3D/X3DImporter.hpp index c9509a035..adb73e6f8 100644 --- a/code/AssetLib/X3D/X3DImporter.hpp +++ b/code/AssetLib/X3D/X3DImporter.hpp @@ -270,6 +270,7 @@ public: void Clear(); private: + X3DNodeElementBase *MACRO_USE_CHECKANDAPPLY(XmlNode &node, std::string pDEF, std::string pUSE, X3DElemType pType, X3DNodeElementBase *pNE); bool isNodeEmpty(XmlNode &node); void checkNodeMustBeEmpty(XmlNode &node); void skipUnsupportedNode(const std::string &pParentNodeName, XmlNode &node); diff --git a/code/AssetLib/X3D/X3DImporter_Geometry2D.cpp b/code/AssetLib/X3D/X3DImporter_Geometry2D.cpp index 171075556..8d0f5bad9 100644 --- a/code/AssetLib/X3D/X3DImporter_Geometry2D.cpp +++ b/code/AssetLib/X3D/X3DImporter_Geometry2D.cpp @@ -78,7 +78,7 @@ void X3DImporter::readArc2D(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Arc2D, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Arc2D, ne); } else { // create and if needed - define new geometry object. ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_Arc2D, mNodeElementCur); @@ -138,7 +138,7 @@ void X3DImporter::readArcClose2D(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_ArcClose2D, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_ArcClose2D, ne); } else { // create and if needed - define new geometry object. ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_ArcClose2D, mNodeElementCur); @@ -185,7 +185,7 @@ void X3DImporter::readCircle2D(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Circle2D, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Circle2D, ne); } else { // create and if needed - define new geometry object. ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_Circle2D, mNodeElementCur); @@ -234,7 +234,7 @@ void X3DImporter::readDisk2D(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Disk2D, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Disk2D, ne); } else { std::list tlist_o, tlist_i; @@ -308,7 +308,7 @@ void X3DImporter::readPolyline2D(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Polyline2D, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Polyline2D, ne); } else { // create and if needed - define new geometry object. ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_Polyline2D, mNodeElementCur); @@ -351,7 +351,7 @@ void X3DImporter::readPolypoint2D(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Polypoint2D, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Polypoint2D, ne); } else { // create and if needed - define new geometry object. ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_Polypoint2D, mNodeElementCur); @@ -391,7 +391,7 @@ void X3DImporter::readRectangle2D(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Rectangle2D, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Rectangle2D, ne); } else { // create and if needed - define new geometry object. ne = new X3DNodeElementGeometry2D(X3DElemType::ENET_Rectangle2D, mNodeElementCur); @@ -437,7 +437,7 @@ void X3DImporter::readTriangleSet2D(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TriangleSet2D, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TriangleSet2D, ne); } else { if (vertices.size() % 3) throw DeadlyImportError("TriangleSet2D. Not enough points for defining triangle."); diff --git a/code/AssetLib/X3D/X3DImporter_Geometry3D.cpp b/code/AssetLib/X3D/X3DImporter_Geometry3D.cpp index 53f5b27bb..edde34281 100644 --- a/code/AssetLib/X3D/X3DImporter_Geometry3D.cpp +++ b/code/AssetLib/X3D/X3DImporter_Geometry3D.cpp @@ -46,16 +46,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_X3D_IMPORTER +#include "X3DGeoHelper.h" #include "X3DImporter.hpp" #include "X3DImporter_Macro.hpp" #include "X3DXmlHelper.h" -#include "X3DGeoHelper.h" // Header files, Assimp. #include -namespace Assimp -{ +namespace Assimp { // ID = def; + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Box, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry3D(X3DElemType::ENET_Box, mNodeElementCur); + if (!def.empty()) ne->ID = def; - X3DGeoHelper::rect_parallel_epiped(size, ((X3DNodeElementGeometry3D *)ne)->Vertices); // get quad list + X3DGeoHelper::rect_parallel_epiped(size, ((X3DNodeElementGeometry3D *)ne)->Vertices); // get quad list ((X3DNodeElementGeometry3D *)ne)->Solid = solid; ((X3DNodeElementGeometry3D *)ne)->NumIndices = 4; - // check for X3DMetadataObject childs. - if(!isNodeEmpty(node)) - childrenReadMetadata(node, ne, "Box"); - else - mNodeElementCur->Children.push_back(ne);// add made object as child to current element + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Box"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element - NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph - }// if(!use.empty()) else + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else } // tvec;// temp array for vertices. + std::vector tvec; // temp array for vertices. - // create and if needed - define new geometry object. - ne = new X3DNodeElementGeometry3D(X3DElemType::ENET_Cone, mNodeElementCur); - if(!def.empty()) ne->ID = def; + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry3D(X3DElemType::ENET_Cone, mNodeElementCur); + if (!def.empty()) ne->ID = def; - // make cone or parts according to flags. - if(side) - { - StandardShapes::MakeCone(height, 0, bottomRadius, tess, tvec, !bottom); - } - else if(bottom) - { - StandardShapes::MakeCircle(bottomRadius, tess, tvec); - height = -(height / 2); - for(std::vector::iterator it = tvec.begin(); it != tvec.end(); ++it) it->y = height;// y - because circle made in oXZ. - } + // make cone or parts according to flags. + if (side) { + StandardShapes::MakeCone(height, 0, bottomRadius, tess, tvec, !bottom); + } else if (bottom) { + StandardShapes::MakeCircle(bottomRadius, tess, tvec); + height = -(height / 2); + for (std::vector::iterator it = tvec.begin(); it != tvec.end(); ++it) + it->y = height; // y - because circle made in oXZ. + } - // copy data from temp array + // copy data from temp array for (std::vector::iterator it = tvec.begin(); it != tvec.end(); ++it) - ((X3DNodeElementGeometry3D*)ne)->Vertices.push_back(*it); + ((X3DNodeElementGeometry3D *)ne)->Vertices.push_back(*it); - ((X3DNodeElementGeometry3D *)ne)->Solid = solid; + ((X3DNodeElementGeometry3D *)ne)->Solid = solid; ((X3DNodeElementGeometry3D *)ne)->NumIndices = 3; - // check for X3DMetadataObject childs. - if(!isNodeEmpty(node)) - childrenReadMetadata(node, ne, "Cone"); - else - mNodeElementCur->Children.push_back(ne);// add made object as child to current element + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Cone"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element - NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph - }// if(!use.empty()) else + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else } // tside;// temp array for vertices of side. - std::vector tcir;// temp array for vertices of circle. + std::vector tside; // temp array for vertices of side. + std::vector tcir; // temp array for vertices of circle. - // create and if needed - define new geometry object. - ne = new X3DNodeElementGeometry3D(X3DElemType::ENET_Cylinder, mNodeElementCur); - if(!def.empty()) ne->ID = def; + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry3D(X3DElemType::ENET_Cylinder, mNodeElementCur); + if (!def.empty()) ne->ID = def; - // make cilynder or parts according to flags. - if(side) StandardShapes::MakeCone(height, radius, radius, tess, tside, true); + // make cilynder or parts according to flags. + if (side) StandardShapes::MakeCone(height, radius, radius, tess, tside, true); - height /= 2;// height defined for whole cylinder, when creating top and bottom circle we are using just half of height. - if(top || bottom) StandardShapes::MakeCircle(radius, tess, tcir); - // copy data from temp arrays + height /= 2; // height defined for whole cylinder, when creating top and bottom circle we are using just half of height. + if (top || bottom) StandardShapes::MakeCircle(radius, tess, tcir); + // copy data from temp arrays std::list &vlist = ((X3DNodeElementGeometry3D *)ne)->Vertices; // just short alias. - for(std::vector::iterator it = tside.begin(); it != tside.end(); ++it) vlist.push_back(*it); + for (std::vector::iterator it = tside.begin(); it != tside.end(); ++it) + vlist.push_back(*it); - if(top) - { - for(std::vector::iterator it = tcir.begin(); it != tcir.end(); ++it) - { - (*it).y = height;// y - because circle made in oXZ. - vlist.push_back(*it); - } - }// if(top) + if (top) { + for (std::vector::iterator it = tcir.begin(); it != tcir.end(); ++it) { + (*it).y = height; // y - because circle made in oXZ. + vlist.push_back(*it); + } + } // if(top) - if(bottom) - { - for(std::vector::iterator it = tcir.begin(); it != tcir.end(); ++it) - { - (*it).y = -height;// y - because circle made in oXZ. - vlist.push_back(*it); - } - }// if(top) + if (bottom) { + for (std::vector::iterator it = tcir.begin(); it != tcir.end(); ++it) { + (*it).y = -height; // y - because circle made in oXZ. + vlist.push_back(*it); + } + } // if(top) - ((X3DNodeElementGeometry3D *)ne)->Solid = solid; + ((X3DNodeElementGeometry3D *)ne)->Solid = solid; ((X3DNodeElementGeometry3D *)ne)->NumIndices = 3; - // check for X3DMetadataObject childs. - if(!isNodeEmpty(node)) - childrenReadMetadata(node, ne, "Cylinder"); - else - mNodeElementCur->Children.push_back(ne);// add made object as child to current element + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Cylinder"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element - NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph - }// if(!use.empty()) else + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else } // must be grater than zero."); - if((xDimension <= 0) || (zDimension <= 0)) throw DeadlyImportError("Dimension in must be grater than zero."); + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_ElevationGrid, ne); + } else { + if ((xSpacing == 0.0f) || (zSpacing == 0.0f)) throw DeadlyImportError("Spacing in must be grater than zero."); + if ((xDimension <= 0) || (zDimension <= 0)) throw DeadlyImportError("Dimension in must be grater than zero."); if ((size_t)(xDimension * zDimension) != height.size()) DeadlyImportError("Heights count must be equal to \"xDimension * zDimension\" in "); - // create and if needed - define new geometry object. - ne = new X3DNodeElementElevationGrid(X3DElemType::ENET_ElevationGrid, mNodeElementCur); - if(!def.empty()) ne->ID = def; + // create and if needed - define new geometry object. + ne = new X3DNodeElementElevationGrid(X3DElemType::ENET_ElevationGrid, mNodeElementCur); + if (!def.empty()) ne->ID = def; - X3DNodeElementElevationGrid &grid_alias = *((X3DNodeElementElevationGrid *)ne); // create alias for conveience + X3DNodeElementElevationGrid &grid_alias = *((X3DNodeElementElevationGrid *)ne); // create alias for conveience - {// create grid vertices list - std::vector::const_iterator he_it = height.begin(); + { // create grid vertices list + std::vector::const_iterator he_it = height.begin(); - for(int32_t zi = 0; zi < zDimension; zi++)// rows - { - for(int32_t xi = 0; xi < xDimension; xi++)// columns - { - aiVector3D tvec(xSpacing * xi, *he_it, zSpacing * zi); + for (int32_t zi = 0; zi < zDimension; zi++) // rows + { + for (int32_t xi = 0; xi < xDimension; xi++) // columns + { + aiVector3D tvec(xSpacing * xi, *he_it, zSpacing * zi); - grid_alias.Vertices.push_back(tvec); - ++he_it; - } - } - }// END: create grid vertices list - // - // create faces list. In "coordIdx" format - // - // check if we have quads - if((xDimension < 2) || (zDimension < 2))// only one element in dimension is set, create line set. - { - ((X3DNodeElementElevationGrid *)ne)->NumIndices = 2; // will be holded as line set. - for(size_t i = 0, i_e = (grid_alias.Vertices.size() - 1); i < i_e; i++) - { - grid_alias.CoordIdx.push_back(static_cast(i)); - grid_alias.CoordIdx.push_back(static_cast(i + 1)); - grid_alias.CoordIdx.push_back(-1); - } - } - else// two or more elements in every dimension is set. create quad set. - { - ((X3DNodeElementElevationGrid *)ne)->NumIndices = 4; - for(int32_t fzi = 0, fzi_e = (zDimension - 1); fzi < fzi_e; fzi++)// rows - { - for(int32_t fxi = 0, fxi_e = (xDimension - 1); fxi < fxi_e; fxi++)// columns - { - // points direction in face. - if(ccw) - { - // CCW: - // 3 2 - // 0 1 - grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + fxi); - grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + (fxi + 1)); - grid_alias.CoordIdx.push_back(fzi * xDimension + (fxi + 1)); - grid_alias.CoordIdx.push_back(fzi * xDimension + fxi); - } - else - { - // CW: - // 0 1 - // 3 2 - grid_alias.CoordIdx.push_back(fzi * xDimension + fxi); - grid_alias.CoordIdx.push_back(fzi * xDimension + (fxi + 1)); - grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + (fxi + 1)); - grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + fxi); - }// if(ccw) else - - grid_alias.CoordIdx.push_back(-1); - }// for(int32_t fxi = 0, fxi_e = (xDimension - 1); fxi < fxi_e; fxi++) - }// for(int32_t fzi = 0, fzi_e = (zDimension - 1); fzi < fzi_e; fzi++) - }// if((xDimension < 2) || (zDimension < 2)) else - - grid_alias.ColorPerVertex = colorPerVertex; - grid_alias.NormalPerVertex = normalPerVertex; - grid_alias.CreaseAngle = creaseAngle; - grid_alias.Solid = solid; - // check for child nodes - if(!isNodeEmpty(node)) + grid_alias.Vertices.push_back(tvec); + ++he_it; + } + } + } // END: create grid vertices list + // + // create faces list. In "coordIdx" format + // + // check if we have quads + if ((xDimension < 2) || (zDimension < 2)) // only one element in dimension is set, create line set. { - ParseHelper_Node_Enter(ne); + ((X3DNodeElementElevationGrid *)ne)->NumIndices = 2; // will be holded as line set. + for (size_t i = 0, i_e = (grid_alias.Vertices.size() - 1); i < i_e; i++) { + grid_alias.CoordIdx.push_back(static_cast(i)); + grid_alias.CoordIdx.push_back(static_cast(i + 1)); + grid_alias.CoordIdx.push_back(-1); + } + } else // two or more elements in every dimension is set. create quad set. + { + ((X3DNodeElementElevationGrid *)ne)->NumIndices = 4; + for (int32_t fzi = 0, fzi_e = (zDimension - 1); fzi < fzi_e; fzi++) // rows + { + for (int32_t fxi = 0, fxi_e = (xDimension - 1); fxi < fxi_e; fxi++) // columns + { + // points direction in face. + if (ccw) { + // CCW: + // 3 2 + // 0 1 + grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + fxi); + grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + (fxi + 1)); + grid_alias.CoordIdx.push_back(fzi * xDimension + (fxi + 1)); + grid_alias.CoordIdx.push_back(fzi * xDimension + fxi); + } else { + // CW: + // 0 1 + // 3 2 + grid_alias.CoordIdx.push_back(fzi * xDimension + fxi); + grid_alias.CoordIdx.push_back(fzi * xDimension + (fxi + 1)); + grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + (fxi + 1)); + grid_alias.CoordIdx.push_back((fzi + 1) * xDimension + fxi); + } // if(ccw) else + + grid_alias.CoordIdx.push_back(-1); + } // for(int32_t fxi = 0, fxi_e = (xDimension - 1); fxi < fxi_e; fxi++) + } // for(int32_t fzi = 0, fzi_e = (zDimension - 1); fzi < fzi_e; fzi++) + } // if((xDimension < 2) || (zDimension < 2)) else + + grid_alias.ColorPerVertex = colorPerVertex; + grid_alias.NormalPerVertex = normalPerVertex; + grid_alias.CreaseAngle = creaseAngle; + grid_alias.Solid = solid; + // check for child nodes + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); for (auto currentChildNode : node.children()) { const std::string ¤tChildName = currentChildNode.name(); - // check for X3DComposedGeometryNodes - if (currentChildName == "Color") readColor(currentChildNode); - else if (currentChildName == "ColorRGBA") readColorRGBA(currentChildNode); - else if (currentChildName == "Normal") readNormal(currentChildNode); - else if (currentChildName == "TextureCoordinate") readTextureCoordinate(currentChildNode); - // check for X3DMetadataObject - else if (!checkForMetadataNode(currentChildNode)) skipUnsupportedNode("ElevationGrid", currentChildNode); + // check for X3DComposedGeometryNodes + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Normal") + readNormal(currentChildNode); + else if (currentChildName == "TextureCoordinate") + readTextureCoordinate(currentChildNode); + // check for X3DMetadataObject + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("ElevationGrid", currentChildNode); } ParseHelper_Node_Exit(); - }// if(!mReader->isEmptyElement()) - else - { - mNodeElementCur->Children.push_back(ne);// add made object as child to current element - }// if(!mReader->isEmptyElement()) else + } // if(!mReader->isEmptyElement()) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } // if(!mReader->isEmptyElement()) else - NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph - }// if(!use.empty()) else + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else } -template -static void GeometryHelper_Extrusion_CurveIsClosed(std::vector& pCurve, const bool pDropTail, const bool pRemoveLastPoint, bool& pCurveIsClosed) -{ +template +static void GeometryHelper_Extrusion_CurveIsClosed(std::vector &pCurve, const bool pDropTail, const bool pRemoveLastPoint, bool &pCurveIsClosed) { size_t cur_sz = pCurve.size(); - pCurveIsClosed = false; - // for curve with less than four points checking is have no sense, - if(cur_sz < 4) return; + pCurveIsClosed = false; + // for curve with less than four points checking is have no sense, + if (cur_sz < 4) return; - for(size_t s = 3, s_e = cur_sz; s < s_e; s++) - { - // search for first point of duplicated part. - if(pCurve[0] == pCurve[s]) - { - bool found = true; + for (size_t s = 3, s_e = cur_sz; s < s_e; s++) { + // search for first point of duplicated part. + if (pCurve[0] == pCurve[s]) { + bool found = true; - // check if tail(indexed by b2) is duplicate of head(indexed by b1). - for(size_t b1 = 1, b2 = (s + 1); b2 < cur_sz; b1++, b2++) - { - if(pCurve[b1] != pCurve[b2]) - {// points not match: clear flag and break loop. - found = false; + // check if tail(indexed by b2) is duplicate of head(indexed by b1). + for (size_t b1 = 1, b2 = (s + 1); b2 < cur_sz; b1++, b2++) { + if (pCurve[b1] != pCurve[b2]) { // points not match: clear flag and break loop. + found = false; - break; - } - }// for(size_t b1 = 1, b2 = (s + 1); b2 < cur_sz; b1++, b2++) + break; + } + } // for(size_t b1 = 1, b2 = (s + 1); b2 < cur_sz; b1++, b2++) - // if duplicate tail is found then drop or not it depending on flags. - if(found) - { - pCurveIsClosed = true; - if(pDropTail) - { - if(!pRemoveLastPoint) s++;// prepare value for iterator's arithmetics. + // if duplicate tail is found then drop or not it depending on flags. + if (found) { + pCurveIsClosed = true; + if (pDropTail) { + if (!pRemoveLastPoint) s++; // prepare value for iterator's arithmetics. - pCurve.erase(pCurve.begin() + s, pCurve.end());// remove tail - } + pCurve.erase(pCurve.begin() + s, pCurve.end()); // remove tail + } - break; - }// if(found) - }// if(pCurve[0] == pCurve[s]) - }// for(size_t s = 3, s_e = (cur_sz - 1); s < s_e; s++) + break; + } // if(found) + } // if(pCurve[0] == pCurve[s]) + } // for(size_t s = 3, s_e = (cur_sz - 1); s < s_e; s++) } -static aiVector3D GeometryHelper_Extrusion_GetNextY(const size_t pSpine_PointIdx, const std::vector& pSpine, const bool pSpine_Closed) -{ +static aiVector3D GeometryHelper_Extrusion_GetNextY(const size_t pSpine_PointIdx, const std::vector &pSpine, const bool pSpine_Closed) { const size_t spine_idx_last = pSpine.size() - 1; aiVector3D tvec; - if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last))// at first special cases - { - if(pSpine_Closed) - {// If the spine curve is closed: The SCP for the first and last points is the same and is found using (spine[1] - spine[n - 2]) to compute the Y-axis. - // As we even for closed spine curve last and first point in pSpine are not the same: duplicates(spine[n - 1] which are equivalent to spine[0]) - // in tail are removed. - // So, last point in pSpine is a spine[n - 2] - tvec = pSpine[1] - pSpine[spine_idx_last]; - } - else if(pSpine_PointIdx == 0) - {// The Y-axis used for the first point is the vector from spine[0] to spine[1] - tvec = pSpine[1] - pSpine[0]; - } - else - {// The Y-axis used for the last point it is the vector from spine[n-2] to spine[n-1]. In our case(see above about dropping tail) spine[n - 1] is - // the spine[0]. - tvec = pSpine[spine_idx_last] - pSpine[spine_idx_last - 1]; - } - }// if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last)) - else - {// For all points other than the first or last: The Y-axis for spine[i] is found by normalizing the vector defined by (spine[i+1] - spine[i-1]). - tvec = pSpine[pSpine_PointIdx + 1] - pSpine[pSpine_PointIdx - 1]; - }// if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last)) else + if ((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last)) // at first special cases + { + if (pSpine_Closed) { // If the spine curve is closed: The SCP for the first and last points is the same and is found using (spine[1] - spine[n - 2]) to compute the Y-axis. + // As we even for closed spine curve last and first point in pSpine are not the same: duplicates(spine[n - 1] which are equivalent to spine[0]) + // in tail are removed. + // So, last point in pSpine is a spine[n - 2] + tvec = pSpine[1] - pSpine[spine_idx_last]; + } else if (pSpine_PointIdx == 0) { // The Y-axis used for the first point is the vector from spine[0] to spine[1] + tvec = pSpine[1] - pSpine[0]; + } else { // The Y-axis used for the last point it is the vector from spine[n-2] to spine[n-1]. In our case(see above about dropping tail) spine[n - 1] is + // the spine[0]. + tvec = pSpine[spine_idx_last] - pSpine[spine_idx_last - 1]; + } + } // if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last)) + else { // For all points other than the first or last: The Y-axis for spine[i] is found by normalizing the vector defined by (spine[i+1] - spine[i-1]). + tvec = pSpine[pSpine_PointIdx + 1] - pSpine[pSpine_PointIdx - 1]; + } // if((pSpine_PointIdx == 0) || (pSpine_PointIdx == spine_idx_last)) else - return tvec.Normalize(); + return tvec.Normalize(); } -static aiVector3D GeometryHelper_Extrusion_GetNextZ(const size_t pSpine_PointIdx, const std::vector& pSpine, const bool pSpine_Closed, - const aiVector3D pVecZ_Prev) -{ +static aiVector3D GeometryHelper_Extrusion_GetNextZ(const size_t pSpine_PointIdx, const std::vector &pSpine, const bool pSpine_Closed, + const aiVector3D pVecZ_Prev) { const aiVector3D zero_vec(0); const size_t spine_idx_last = pSpine.size() - 1; aiVector3D tvec; - // at first special cases - if(pSpine.size() < 3)// spine have not enough points for vector calculations. - { - tvec.Set(0, 0, 1); - } - else if(pSpine_PointIdx == 0)// special case: first point - { - if(pSpine_Closed)// for calculating use previous point in curve s[n - 2]. In list it's a last point, because point s[n - 1] was removed as duplicate. - { - tvec = (pSpine[1] - pSpine[0]) ^ (pSpine[spine_idx_last] - pSpine[0]); - } - else // for not closed curve first and next point(s[0] and s[1]) has the same vector Z. - { - bool found = false; + // at first special cases + if (pSpine.size() < 3) // spine have not enough points for vector calculations. + { + tvec.Set(0, 0, 1); + } else if (pSpine_PointIdx == 0) // special case: first point + { + if (pSpine_Closed) // for calculating use previous point in curve s[n - 2]. In list it's a last point, because point s[n - 1] was removed as duplicate. + { + tvec = (pSpine[1] - pSpine[0]) ^ (pSpine[spine_idx_last] - pSpine[0]); + } else // for not closed curve first and next point(s[0] and s[1]) has the same vector Z. + { + bool found = false; - // As said: "If the Z-axis of the first point is undefined (because the spine is not closed and the first two spine segments are collinear) - // then the Z-axis for the first spine point with a defined Z-axis is used." - // Walk through spine and find Z. - for(size_t next_point = 2; (next_point <= spine_idx_last) && !found; next_point++) - { - // (pSpine[2] - pSpine[1]) ^ (pSpine[0] - pSpine[1]) - tvec = (pSpine[next_point] - pSpine[next_point - 1]) ^ (pSpine[next_point - 2] - pSpine[next_point - 1]); - found = !tvec.Equal(zero_vec); - } + // As said: "If the Z-axis of the first point is undefined (because the spine is not closed and the first two spine segments are collinear) + // then the Z-axis for the first spine point with a defined Z-axis is used." + // Walk through spine and find Z. + for (size_t next_point = 2; (next_point <= spine_idx_last) && !found; next_point++) { + // (pSpine[2] - pSpine[1]) ^ (pSpine[0] - pSpine[1]) + tvec = (pSpine[next_point] - pSpine[next_point - 1]) ^ (pSpine[next_point - 2] - pSpine[next_point - 1]); + found = !tvec.Equal(zero_vec); + } - // if entire spine are collinear then use OZ axis. - if(!found) tvec.Set(0, 0, 1); - }// if(pSpine_Closed) else - }// else if(pSpine_PointIdx == 0) - else if(pSpine_PointIdx == spine_idx_last)// special case: last point - { - if(pSpine_Closed) - {// do not forget that real last point s[n - 1] is removed as duplicated. And in this case we are calculating vector Z for point s[n - 2]. - tvec = (pSpine[0] - pSpine[pSpine_PointIdx]) ^ (pSpine[pSpine_PointIdx - 1] - pSpine[pSpine_PointIdx]); - // if taken spine vectors are collinear then use previous vector Z. - if(tvec.Equal(zero_vec)) tvec = pVecZ_Prev; - } - else - {// vector Z for last point of not closed curve is previous vector Z. - tvec = pVecZ_Prev; - } - } - else// regular point - { - tvec = (pSpine[pSpine_PointIdx + 1] - pSpine[pSpine_PointIdx]) ^ (pSpine[pSpine_PointIdx - 1] - pSpine[pSpine_PointIdx]); - // if taken spine vectors are collinear then use previous vector Z. - if(tvec.Equal(zero_vec)) tvec = pVecZ_Prev; - } + // if entire spine are collinear then use OZ axis. + if (!found) tvec.Set(0, 0, 1); + } // if(pSpine_Closed) else + } // else if(pSpine_PointIdx == 0) + else if (pSpine_PointIdx == spine_idx_last) // special case: last point + { + if (pSpine_Closed) { // do not forget that real last point s[n - 1] is removed as duplicated. And in this case we are calculating vector Z for point s[n - 2]. + tvec = (pSpine[0] - pSpine[pSpine_PointIdx]) ^ (pSpine[pSpine_PointIdx - 1] - pSpine[pSpine_PointIdx]); + // if taken spine vectors are collinear then use previous vector Z. + if (tvec.Equal(zero_vec)) tvec = pVecZ_Prev; + } else { // vector Z for last point of not closed curve is previous vector Z. + tvec = pVecZ_Prev; + } + } else // regular point + { + tvec = (pSpine[pSpine_PointIdx + 1] - pSpine[pSpine_PointIdx]) ^ (pSpine[pSpine_PointIdx - 1] - pSpine[pSpine_PointIdx]); + // if taken spine vectors are collinear then use previous vector Z. + if (tvec.Equal(zero_vec)) tvec = pVecZ_Prev; + } - // After determining the Z-axis, its dot product with the Z-axis of the previous spine point is computed. If this value is negative, the Z-axis - // is flipped (multiplied by -1). - if((tvec * pVecZ_Prev) < 0) tvec = -tvec; + // After determining the Z-axis, its dot product with the Z-axis of the previous spine point is computed. If this value is negative, the Z-axis + // is flipped (multiplied by -1). + if ((tvec * pVecZ_Prev) < 0) tvec = -tvec; - return tvec.Normalize(); + return tvec.Normalize(); } // scale; bool solid = true; std::vector spine; - X3DNodeElementBase* ne( nullptr ); + X3DNodeElementBase *ne(nullptr); MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); XmlParser::getBoolAttribute(node, "beginCap", beginCap); @@ -588,244 +546,225 @@ void X3DImporter::readExtrusion(XmlNode &node) { XmlParser::getBoolAttribute(node, "solid", solid); X3DXmlHelper::getVector3DArrayAttribute(node, "spine", spine); - // if "USE" defined then find already defined element. - if(!use.empty()) - { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Extrusion, ne); - } - else - { - // - // check if default values must be assigned - // - if(spine.size() == 0) - { - spine.resize(2); - spine[0].Set(0, 0, 0), spine[1].Set(0, 1, 0); - } - else if(spine.size() == 1) - { - throw DeadlyImportError("ParseNode_Geometry3D_Extrusion. Spine must have at least two points."); - } + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Extrusion, ne); + } else { + // + // check if default values must be assigned + // + if (spine.size() == 0) { + spine.resize(2); + spine[0].Set(0, 0, 0), spine[1].Set(0, 1, 0); + } else if (spine.size() == 1) { + throw DeadlyImportError("ParseNode_Geometry3D_Extrusion. Spine must have at least two points."); + } - if(crossSection.size() == 0) - { - crossSection.resize(5); - crossSection[0].Set(1, 1), crossSection[1].Set(1, -1), crossSection[2].Set(-1, -1), crossSection[3].Set(-1, 1), crossSection[4].Set(1, 1); - } + if (crossSection.size() == 0) { + crossSection.resize(5); + crossSection[0].Set(1, 1), crossSection[1].Set(1, -1), crossSection[2].Set(-1, -1), crossSection[3].Set(-1, 1), crossSection[4].Set(1, 1); + } - {// orientation - size_t ori_size = orientation.size() / 4; + { // orientation + size_t ori_size = orientation.size() / 4; - if(ori_size < spine.size()) - { - float add_ori[4];// values that will be added + if (ori_size < spine.size()) { + float add_ori[4]; // values that will be added - if(ori_size == 1)// if "orientation" has one element(means one MFRotation with four components) then use it value for all spine points. - { - add_ori[0] = orientation[0], add_ori[1] = orientation[1], add_ori[2] = orientation[2], add_ori[3] = orientation[3]; - } - else// else - use default values - { - add_ori[0] = 0, add_ori[1] = 0, add_ori[2] = 1, add_ori[3] = 0; - } + if (ori_size == 1) // if "orientation" has one element(means one MFRotation with four components) then use it value for all spine points. + { + add_ori[0] = orientation[0], add_ori[1] = orientation[1], add_ori[2] = orientation[2], add_ori[3] = orientation[3]; + } else // else - use default values + { + add_ori[0] = 0, add_ori[1] = 0, add_ori[2] = 1, add_ori[3] = 0; + } - orientation.reserve(spine.size() * 4); - for(size_t i = 0, i_e = (spine.size() - ori_size); i < i_e; i++) - orientation.push_back(add_ori[0]), orientation.push_back(add_ori[1]), orientation.push_back(add_ori[2]), orientation.push_back(add_ori[3]); - } + orientation.reserve(spine.size() * 4); + for (size_t i = 0, i_e = (spine.size() - ori_size); i < i_e; i++) + orientation.push_back(add_ori[0]), orientation.push_back(add_ori[1]), orientation.push_back(add_ori[2]), orientation.push_back(add_ori[3]); + } - if(orientation.size() % 4) throw DeadlyImportError("Attribute \"orientation\" in must has multiple four quantity of numbers."); - }// END: orientation + if (orientation.size() % 4) throw DeadlyImportError("Attribute \"orientation\" in must has multiple four quantity of numbers."); + } // END: orientation - {// scale - if(scale.size() < spine.size()) - { - aiVector2D add_sc; + { // scale + if (scale.size() < spine.size()) { + aiVector2D add_sc; - if(scale.size() == 1)// if "scale" has one element then use it value for all spine points. - add_sc = scale[0]; - else// else - use default values - add_sc.Set(1, 1); + if (scale.size() == 1) // if "scale" has one element then use it value for all spine points. + add_sc = scale[0]; + else // else - use default values + add_sc.Set(1, 1); - scale.reserve(spine.size()); - for(size_t i = 0, i_e = (spine.size() - scale.size()); i < i_e; i++) scale.push_back(add_sc); - } - }// END: scale - // - // create and if needed - define new geometry object. - // - ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_Extrusion, mNodeElementCur); - if(!def.empty()) ne->ID = def; + scale.reserve(spine.size()); + for (size_t i = 0, i_e = (spine.size() - scale.size()); i < i_e; i++) + scale.push_back(add_sc); + } + } // END: scale + // + // create and if needed - define new geometry object. + // + ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_Extrusion, mNodeElementCur); + if (!def.empty()) ne->ID = def; - X3DNodeElementIndexedSet &ext_alias = *((X3DNodeElementIndexedSet *)ne); // create alias for conveience - // assign part of input data - ext_alias.CCW = ccw; - ext_alias.Convex = convex; - ext_alias.CreaseAngle = creaseAngle; - ext_alias.Solid = solid; + X3DNodeElementIndexedSet &ext_alias = *((X3DNodeElementIndexedSet *)ne); // create alias for conveience + // assign part of input data + ext_alias.CCW = ccw; + ext_alias.Convex = convex; + ext_alias.CreaseAngle = creaseAngle; + ext_alias.Solid = solid; - // - // How we done it at all? - // 1. At first we will calculate array of basises for every point in spine(look SCP in ISO-dic). Also "orientation" vector - // are applied vor every basis. - // 2. After that we can create array of point sets: which are scaled, transferred to basis of relative basis and at final translated to real position - // using relative spine point. - // 3. Next step is creating CoordIdx array(do not forget "-1" delimiter). While creating CoordIdx also created faces for begin and end caps, if - // needed. While createing CootdIdx is taking in account CCW flag. - // 4. The last step: create Vertices list. - // - bool spine_closed;// flag: true if spine curve is closed. - bool cross_closed;// flag: true if cross curve is closed. - std::vector basis_arr;// array of basises. ROW_a - X, ROW_b - Y, ROW_c - Z. - std::vector > pointset_arr;// array of point sets: cross curves. + // + // How we done it at all? + // 1. At first we will calculate array of basises for every point in spine(look SCP in ISO-dic). Also "orientation" vector + // are applied vor every basis. + // 2. After that we can create array of point sets: which are scaled, transferred to basis of relative basis and at final translated to real position + // using relative spine point. + // 3. Next step is creating CoordIdx array(do not forget "-1" delimiter). While creating CoordIdx also created faces for begin and end caps, if + // needed. While createing CootdIdx is taking in account CCW flag. + // 4. The last step: create Vertices list. + // + bool spine_closed; // flag: true if spine curve is closed. + bool cross_closed; // flag: true if cross curve is closed. + std::vector basis_arr; // array of basises. ROW_a - X, ROW_b - Y, ROW_c - Z. + std::vector> pointset_arr; // array of point sets: cross curves. // detect closed curves - GeometryHelper_Extrusion_CurveIsClosed(crossSection, true, true, cross_closed);// true - drop tail, true - remove duplicate end. - GeometryHelper_Extrusion_CurveIsClosed(spine, true, true, spine_closed);// true - drop tail, true - remove duplicate end. + GeometryHelper_Extrusion_CurveIsClosed(crossSection, true, true, cross_closed); // true - drop tail, true - remove duplicate end. + GeometryHelper_Extrusion_CurveIsClosed(spine, true, true, spine_closed); // true - drop tail, true - remove duplicate end. // If both cap are requested and spine curve is closed then we can make only one cap. Because second cap will be the same surface. - if(spine_closed) - { - beginCap |= endCap; - endCap = false; - } + if (spine_closed) { + beginCap |= endCap; + endCap = false; + } - {// 1. Calculate array of basises. - aiMatrix4x4 rotmat; - aiVector3D vecX(0), vecY(0), vecZ(0); + { // 1. Calculate array of basises. + aiMatrix4x4 rotmat; + aiVector3D vecX(0), vecY(0), vecZ(0); - basis_arr.resize(spine.size()); - for(size_t i = 0, i_e = spine.size(); i < i_e; i++) - { - aiVector3D tvec; + basis_arr.resize(spine.size()); + for (size_t i = 0, i_e = spine.size(); i < i_e; i++) { + aiVector3D tvec; - // get axises of basis. - vecY = GeometryHelper_Extrusion_GetNextY(i, spine, spine_closed); - vecZ = GeometryHelper_Extrusion_GetNextZ(i, spine, spine_closed, vecZ); - vecX = (vecY ^ vecZ).Normalize(); - // get rotation matrix and apply "orientation" to basis - aiMatrix4x4::Rotation(orientation[i * 4 + 3], aiVector3D(orientation[i * 4], orientation[i * 4 + 1], orientation[i * 4 + 2]), rotmat); - tvec = vecX, tvec *= rotmat, basis_arr[i].a1 = tvec.x, basis_arr[i].a2 = tvec.y, basis_arr[i].a3 = tvec.z; - tvec = vecY, tvec *= rotmat, basis_arr[i].b1 = tvec.x, basis_arr[i].b2 = tvec.y, basis_arr[i].b3 = tvec.z; - tvec = vecZ, tvec *= rotmat, basis_arr[i].c1 = tvec.x, basis_arr[i].c2 = tvec.y, basis_arr[i].c3 = tvec.z; - }// for(size_t i = 0, i_e = spine.size(); i < i_e; i++) - }// END: 1. Calculate array of basises + // get axises of basis. + vecY = GeometryHelper_Extrusion_GetNextY(i, spine, spine_closed); + vecZ = GeometryHelper_Extrusion_GetNextZ(i, spine, spine_closed, vecZ); + vecX = (vecY ^ vecZ).Normalize(); + // get rotation matrix and apply "orientation" to basis + aiMatrix4x4::Rotation(orientation[i * 4 + 3], aiVector3D(orientation[i * 4], orientation[i * 4 + 1], orientation[i * 4 + 2]), rotmat); + tvec = vecX, tvec *= rotmat, basis_arr[i].a1 = tvec.x, basis_arr[i].a2 = tvec.y, basis_arr[i].a3 = tvec.z; + tvec = vecY, tvec *= rotmat, basis_arr[i].b1 = tvec.x, basis_arr[i].b2 = tvec.y, basis_arr[i].b3 = tvec.z; + tvec = vecZ, tvec *= rotmat, basis_arr[i].c1 = tvec.x, basis_arr[i].c2 = tvec.y, basis_arr[i].c3 = tvec.z; + } // for(size_t i = 0, i_e = spine.size(); i < i_e; i++) + } // END: 1. Calculate array of basises - {// 2. Create array of point sets. - aiMatrix4x4 scmat; - std::vector tcross(crossSection.size()); + { // 2. Create array of point sets. + aiMatrix4x4 scmat; + std::vector tcross(crossSection.size()); - pointset_arr.resize(spine.size()); - for(size_t spi = 0, spi_e = spine.size(); spi < spi_e; spi++) - { - aiVector3D tc23vec; + pointset_arr.resize(spine.size()); + for (size_t spi = 0, spi_e = spine.size(); spi < spi_e; spi++) { + aiVector3D tc23vec; - tc23vec.Set(scale[spi].x, 0, scale[spi].y); - aiMatrix4x4::Scaling(tc23vec, scmat); - for(size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; cri++) - { - aiVector3D tvecX, tvecY, tvecZ; + tc23vec.Set(scale[spi].x, 0, scale[spi].y); + aiMatrix4x4::Scaling(tc23vec, scmat); + for (size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; cri++) { + aiVector3D tvecX, tvecY, tvecZ; - tc23vec.Set(crossSection[cri].x, 0, crossSection[cri].y); - // apply scaling to point - tcross[cri] = scmat * tc23vec; - // - // transfer point to new basis - // calculate coordinate in new basis - tvecX.Set(basis_arr[spi].a1, basis_arr[spi].a2, basis_arr[spi].a3), tvecX *= tcross[cri].x; - tvecY.Set(basis_arr[spi].b1, basis_arr[spi].b2, basis_arr[spi].b3), tvecY *= tcross[cri].y; - tvecZ.Set(basis_arr[spi].c1, basis_arr[spi].c2, basis_arr[spi].c3), tvecZ *= tcross[cri].z; - // apply new coordinates and translate it to spine point. - tcross[cri] = tvecX + tvecY + tvecZ + spine[spi]; - }// for(size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; i++) + tc23vec.Set(crossSection[cri].x, 0, crossSection[cri].y); + // apply scaling to point + tcross[cri] = scmat * tc23vec; + // + // transfer point to new basis + // calculate coordinate in new basis + tvecX.Set(basis_arr[spi].a1, basis_arr[spi].a2, basis_arr[spi].a3), tvecX *= tcross[cri].x; + tvecY.Set(basis_arr[spi].b1, basis_arr[spi].b2, basis_arr[spi].b3), tvecY *= tcross[cri].y; + tvecZ.Set(basis_arr[spi].c1, basis_arr[spi].c2, basis_arr[spi].c3), tvecZ *= tcross[cri].z; + // apply new coordinates and translate it to spine point. + tcross[cri] = tvecX + tvecY + tvecZ + spine[spi]; + } // for(size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; i++) - pointset_arr[spi] = tcross;// store transferred point set - }// for(size_t spi = 0, spi_e = spine.size(); spi < spi_e; i++) - }// END: 2. Create array of point sets. + pointset_arr[spi] = tcross; // store transferred point set + } // for(size_t spi = 0, spi_e = spine.size(); spi < spi_e; i++) + } // END: 2. Create array of point sets. - {// 3. Create CoordIdx. - // add caps if needed - if(beginCap) - { - // add cap as polygon. vertices of cap are places at begin, so just add numbers from zero. - for(size_t i = 0, i_e = crossSection.size(); i < i_e; i++) ext_alias.CoordIndex.push_back(static_cast(i)); + { // 3. Create CoordIdx. + // add caps if needed + if (beginCap) { + // add cap as polygon. vertices of cap are places at begin, so just add numbers from zero. + for (size_t i = 0, i_e = crossSection.size(); i < i_e; i++) + ext_alias.CoordIndex.push_back(static_cast(i)); - // add delimiter - ext_alias.CoordIndex.push_back(-1); - }// if(beginCap) + // add delimiter + ext_alias.CoordIndex.push_back(-1); + } // if(beginCap) - if(endCap) - { - // add cap as polygon. vertices of cap are places at end, as for beginCap use just sequence of numbers but with offset. - size_t beg = (pointset_arr.size() - 1) * crossSection.size(); + if (endCap) { + // add cap as polygon. vertices of cap are places at end, as for beginCap use just sequence of numbers but with offset. + size_t beg = (pointset_arr.size() - 1) * crossSection.size(); - for(size_t i = beg, i_e = (beg + crossSection.size()); i < i_e; i++) ext_alias.CoordIndex.push_back(static_cast(i)); + for (size_t i = beg, i_e = (beg + crossSection.size()); i < i_e; i++) + ext_alias.CoordIndex.push_back(static_cast(i)); - // add delimiter - ext_alias.CoordIndex.push_back(-1); - }// if(beginCap) + // add delimiter + ext_alias.CoordIndex.push_back(-1); + } // if(beginCap) - // add quads - for(size_t spi = 0, spi_e = (spine.size() - 1); spi <= spi_e; spi++) - { - const size_t cr_sz = crossSection.size(); - const size_t cr_last = crossSection.size() - 1; + // add quads + for (size_t spi = 0, spi_e = (spine.size() - 1); spi <= spi_e; spi++) { + const size_t cr_sz = crossSection.size(); + const size_t cr_last = crossSection.size() - 1; - size_t right_col;// hold index basis for points of quad placed in right column; + size_t right_col; // hold index basis for points of quad placed in right column; - if(spi != spi_e) - right_col = spi + 1; - else if(spine_closed)// if spine curve is closed then one more quad is needed: between first and last points of curve. - right_col = 0; - else - break;// if spine curve is not closed then break the loop, because spi is out of range for that type of spine. + if (spi != spi_e) + right_col = spi + 1; + else if (spine_closed) // if spine curve is closed then one more quad is needed: between first and last points of curve. + right_col = 0; + else + break; // if spine curve is not closed then break the loop, because spi is out of range for that type of spine. - for(size_t cri = 0; cri < cr_sz; cri++) - { - if(cri != cr_last) - { - MACRO_FACE_ADD_QUAD(ccw, ext_alias.CoordIndex, - static_cast(spi * cr_sz + cri), - static_cast(right_col * cr_sz + cri), - static_cast(right_col * cr_sz + cri + 1), - static_cast(spi * cr_sz + cri + 1)); - // add delimiter - ext_alias.CoordIndex.push_back(-1); - } - else if(cross_closed)// if cross curve is closed then one more quad is needed: between first and last points of curve. - { - MACRO_FACE_ADD_QUAD(ccw, ext_alias.CoordIndex, - static_cast(spi * cr_sz + cri), - static_cast(right_col * cr_sz + cri), - static_cast(right_col * cr_sz + 0), - static_cast(spi * cr_sz + 0)); - // add delimiter - ext_alias.CoordIndex.push_back(-1); - } - }// for(size_t cri = 0; cri < cr_sz; cri++) - }// for(size_t spi = 0, spi_e = (spine.size() - 2); spi < spi_e; spi++) - }// END: 3. Create CoordIdx. + for (size_t cri = 0; cri < cr_sz; cri++) { + if (cri != cr_last) { + MACRO_FACE_ADD_QUAD(ccw, ext_alias.CoordIndex, + static_cast(spi * cr_sz + cri), + static_cast(right_col * cr_sz + cri), + static_cast(right_col * cr_sz + cri + 1), + static_cast(spi * cr_sz + cri + 1)); + // add delimiter + ext_alias.CoordIndex.push_back(-1); + } else if (cross_closed) // if cross curve is closed then one more quad is needed: between first and last points of curve. + { + MACRO_FACE_ADD_QUAD(ccw, ext_alias.CoordIndex, + static_cast(spi * cr_sz + cri), + static_cast(right_col * cr_sz + cri), + static_cast(right_col * cr_sz + 0), + static_cast(spi * cr_sz + 0)); + // add delimiter + ext_alias.CoordIndex.push_back(-1); + } + } // for(size_t cri = 0; cri < cr_sz; cri++) + } // for(size_t spi = 0, spi_e = (spine.size() - 2); spi < spi_e; spi++) + } // END: 3. Create CoordIdx. - {// 4. Create vertices list. - // just copy all vertices - for(size_t spi = 0, spi_e = spine.size(); spi < spi_e; spi++) - { - for(size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; cri++) - { - ext_alias.Vertices.emplace_back(pointset_arr[spi][cri]); - } - } - }// END: 4. Create vertices list. -//PrintVectorSet("Ext. CoordIdx", ext_alias.CoordIndex); -//PrintVectorSet("Ext. Vertices", ext_alias.Vertices); - // check for child nodes - if(!isNodeEmpty(node)) - childrenReadMetadata(node, ne, "Extrusion"); - else - mNodeElementCur->Children.push_back(ne);// add made object as child to current element + { // 4. Create vertices list. + // just copy all vertices + for (size_t spi = 0, spi_e = spine.size(); spi < spi_e; spi++) { + for (size_t cri = 0, cri_e = crossSection.size(); cri < cri_e; cri++) { + ext_alias.Vertices.emplace_back(pointset_arr[spi][cri]); + } + } + } // END: 4. Create vertices list. + //PrintVectorSet("Ext. CoordIdx", ext_alias.CoordIndex); + //PrintVectorSet("Ext. Vertices", ext_alias.Vertices); + // check for child nodes + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Extrusion"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element - NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph - }// if(!use.empty()) else + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else } // texCoordIndex; - X3DNodeElementBase* ne( nullptr ); + X3DNodeElementBase *ne(nullptr); MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); XmlParser::getBoolAttribute(node, "ccw", ccw); @@ -874,56 +813,57 @@ void X3DImporter::readIndexedFaceSet(XmlNode &node) { XmlParser::getBoolAttribute(node, "solid", solid); X3DXmlHelper::getInt32ArrayAttribute(node, "texCoordIndex", texCoordIndex); - // if "USE" defined then find already defined element. - if(!use.empty()) - { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedFaceSet, ne); - } - else - { - // check data - if(coordIndex.size() == 0) throw DeadlyImportError("IndexedFaceSet must contain not empty \"coordIndex\" attribute."); + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedFaceSet, ne); + } else { + // check data + if (coordIndex.size() == 0) throw DeadlyImportError("IndexedFaceSet must contain not empty \"coordIndex\" attribute."); - // create and if needed - define new geometry object. - ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_IndexedFaceSet, mNodeElementCur); - if(!def.empty()) ne->ID = def; + // create and if needed - define new geometry object. + ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_IndexedFaceSet, mNodeElementCur); + if (!def.empty()) ne->ID = def; - X3DNodeElementIndexedSet &ne_alias = *((X3DNodeElementIndexedSet *)ne); + X3DNodeElementIndexedSet &ne_alias = *((X3DNodeElementIndexedSet *)ne); - ne_alias.CCW = ccw; - ne_alias.ColorIndex = colorIndex; - ne_alias.ColorPerVertex = colorPerVertex; - ne_alias.Convex = convex; - ne_alias.CoordIndex = coordIndex; - ne_alias.CreaseAngle = creaseAngle; - ne_alias.NormalIndex = normalIndex; - ne_alias.NormalPerVertex = normalPerVertex; - ne_alias.Solid = solid; - ne_alias.TexCoordIndex = texCoordIndex; + ne_alias.CCW = ccw; + ne_alias.ColorIndex = colorIndex; + ne_alias.ColorPerVertex = colorPerVertex; + ne_alias.Convex = convex; + ne_alias.CoordIndex = coordIndex; + ne_alias.CreaseAngle = creaseAngle; + ne_alias.NormalIndex = normalIndex; + ne_alias.NormalPerVertex = normalPerVertex; + ne_alias.Solid = solid; + ne_alias.TexCoordIndex = texCoordIndex; // check for child nodes - if(!isNodeEmpty(node)) - { - ParseHelper_Node_Enter(ne); + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); for (auto currentChildNode : node.children()) { const std::string ¤tChildName = currentChildNode.name(); // check for X3DComposedGeometryNodes - if (currentChildName == "Color") readColor(currentChildNode); - else if (currentChildName == "ColorRGBA") readColorRGBA(currentChildNode); - else if (currentChildName == "Coordinate") readCoordinate(currentChildNode); - else if (currentChildName == "Normal") readNormal(currentChildNode); - else if (currentChildName == "TextureCoordinate") readTextureCoordinate(currentChildNode); + if (currentChildName == "Color") + readColor(currentChildNode); + else if (currentChildName == "ColorRGBA") + readColorRGBA(currentChildNode); + else if (currentChildName == "Coordinate") + readCoordinate(currentChildNode); + else if (currentChildName == "Normal") + readNormal(currentChildNode); + else if (currentChildName == "TextureCoordinate") + readTextureCoordinate(currentChildNode); // check for X3DMetadataObject - else if (!checkForMetadataNode(currentChildNode)) skipUnsupportedNode("IndexedFaceSet", currentChildNode); + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("IndexedFaceSet", currentChildNode); } - ParseHelper_Node_Exit(); - }// if(!isNodeEmpty(node)) - else - { - mNodeElementCur->Children.push_back(ne);// add made object as child to current element - } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } - NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph - }// if(!use.empty()) else + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else } // tlist; + std::vector tlist; - // create and if needed - define new geometry object. - ne = new X3DNodeElementGeometry3D(X3DElemType::ENET_Sphere, mNodeElementCur); - if(!def.empty()) ne->ID = def; + // create and if needed - define new geometry object. + ne = new X3DNodeElementGeometry3D(X3DElemType::ENET_Sphere, mNodeElementCur); + if (!def.empty()) ne->ID = def; - StandardShapes::MakeSphere(tess, tlist); - // copy data from temp array and apply scale - for(std::vector::iterator it = tlist.begin(); it != tlist.end(); ++it) - { + StandardShapes::MakeSphere(tess, tlist); + // copy data from temp array and apply scale + for (std::vector::iterator it = tlist.begin(); it != tlist.end(); ++it) { ((X3DNodeElementGeometry3D *)ne)->Vertices.push_back(*it * radius); - } + } - ((X3DNodeElementGeometry3D *)ne)->Solid = solid; + ((X3DNodeElementGeometry3D *)ne)->Solid = solid; ((X3DNodeElementGeometry3D *)ne)->NumIndices = 3; - // check for X3DMetadataObject childs. - if(!isNodeEmpty(node)) - childrenReadMetadata(node, ne, "Sphere"); - else - mNodeElementCur->Children.push_back(ne);// add made object as child to current element + // check for X3DMetadataObject childs. + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Sphere"); + else + mNodeElementCur->Children.push_back(ne); // add made object as child to current element - NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph - }// if(!use.empty()) else + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else } -}// namespace Assimp +} // namespace Assimp #endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/code/AssetLib/X3D/X3DImporter_Group.cpp b/code/AssetLib/X3D/X3DImporter_Group.cpp index d672d741b..f579dc517 100644 --- a/code/AssetLib/X3D/X3DImporter_Group.cpp +++ b/code/AssetLib/X3D/X3DImporter_Group.cpp @@ -72,9 +72,8 @@ void X3DImporter::startReadGroup(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - X3DNodeElementBase *ne; - - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); + X3DNodeElementBase *ne = nullptr; + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); } else { ParseHelper_Group_Begin(); // create new grouping element and go deeper if node has children. // at this place new group mode created and made current, so we can name it. @@ -111,9 +110,9 @@ void X3DImporter::startReadStaticGroup(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - X3DNodeElementBase *ne; + X3DNodeElementBase *ne = nullptr; - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); } else { ParseHelper_Group_Begin(true); // create new grouping element and go deeper if node has children. // at this place new group mode created and made current, so we can name it. @@ -154,9 +153,9 @@ void X3DImporter::startReadSwitch(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - X3DNodeElementBase *ne; + X3DNodeElementBase *ne=nullptr; - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); } else { ParseHelper_Group_Begin(); // create new grouping element and go deeper if node has children. // at this place new group mode created and made current, so we can name it. @@ -228,7 +227,7 @@ void X3DImporter::startReadTransform(XmlNode &node) { if (!use.empty()) { X3DNodeElementBase *ne(nullptr); - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); } else { ParseHelper_Group_Begin(); // create new grouping element and go deeper if node has children. // at this place new group mode created and made current, so we can name it. diff --git a/code/AssetLib/X3D/X3DImporter_Light.cpp b/code/AssetLib/X3D/X3DImporter_Light.cpp index b9bd17164..f1ed5e4cd 100644 --- a/code/AssetLib/X3D/X3DImporter_Light.cpp +++ b/code/AssetLib/X3D/X3DImporter_Light.cpp @@ -83,7 +83,7 @@ void X3DImporter::readDirectionalLight(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_DirectionalLight, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_DirectionalLight, ne); } else { if (on) { // create and if needed - define new geometry object. @@ -150,7 +150,7 @@ void X3DImporter::readPointLight(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_PointLight, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_PointLight, ne); } else { if (on) { // create and if needed - define new geometry object. @@ -227,7 +227,7 @@ void X3DImporter::readSpotLight(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_SpotLight, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_SpotLight, ne); } else { if (on) { // create and if needed - define new geometry object. diff --git a/code/AssetLib/X3D/X3DImporter_Macro.hpp b/code/AssetLib/X3D/X3DImporter_Macro.hpp index f493a86e9..08aac3da7 100644 --- a/code/AssetLib/X3D/X3DImporter_Macro.hpp +++ b/code/AssetLib/X3D/X3DImporter_Macro.hpp @@ -47,20 +47,36 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef X3DIMPORTER_MACRO_HPP_INCLUDED #define X3DIMPORTER_MACRO_HPP_INCLUDED -/// \def MACRO_USE_CHECKANDAPPLY(pDEF, pUSE, pNE) +#include +#include "X3DImporter.hpp" +#include + +namespace Assimp { + /// Used for regular checking while attribute "USE" is defined. /// \param [in] pNode - pugi xml node to read. /// \param [in] pDEF - string holding "DEF" value. /// \param [in] pUSE - string holding "USE" value. /// \param [in] pType - type of element to find. /// \param [out] pNE - pointer to found node element. -#define MACRO_USE_CHECKANDAPPLY(pNode, pDEF, pUSE, pType, pNE) \ - do { \ - checkNodeMustBeEmpty(pNode); \ - if (!pDEF.empty()) Throw_DEF_And_USE(pNode.name()); \ - if (!FindNodeElement(pUSE, X3DElemType::pType, &pNE)) Throw_USE_NotFound(pNode.name(), pUSE); \ - mNodeElementCur->Children.push_back(pNE); /* add found object as child to current element */ \ - } while (false) +inline X3DNodeElementBase *X3DImporter::MACRO_USE_CHECKANDAPPLY(XmlNode &node, std::string pDEF, std::string pUSE, X3DElemType pType, X3DNodeElementBase *pNE) { + if (nullptr == mNodeElementCur) { + printf("here\n"); + } + + //do { + checkNodeMustBeEmpty(node); + if (!pDEF.empty()) + Assimp::Throw_DEF_And_USE(node.name()); + if (!FindNodeElement(pUSE, pType, &pNE)) + Assimp::Throw_USE_NotFound(node.name(), pUSE); + mNodeElementCur->Children.push_back(pNE); /* add found object as child to current element */ + //} while (false); + + return pNE; +} + +} // namespace Assimp /// \def MACRO_ATTRREAD_CHECKUSEDEF_RET /// Compact variant for checking "USE" and "DEF". diff --git a/code/AssetLib/X3D/X3DImporter_Metadata.cpp b/code/AssetLib/X3D/X3DImporter_Metadata.cpp index bb1ba9a9d..8e07d8bb8 100644 --- a/code/AssetLib/X3D/X3DImporter_Metadata.cpp +++ b/code/AssetLib/X3D/X3DImporter_Metadata.cpp @@ -93,7 +93,7 @@ void X3DImporter::childrenReadMetadata(XmlNode &node, X3DNodeElementBase *pParen #define MACRO_METADATA_FINDCREATE(pNode, pDEF_Var, pUSE_Var, pReference, pValue, pNE, pMetaClass, pMetaName, pType) \ /* if "USE" defined then find already defined element. */ \ if (!pUSE_Var.empty()) { \ - MACRO_USE_CHECKANDAPPLY(pNode, pDEF_Var, pUSE_Var, pType, pNE); \ + ne = MACRO_USE_CHECKANDAPPLY(pNode, pDEF_Var, pUSE_Var, pType, pNE); \ } else { \ pNE = new pMetaClass(mNodeElementCur); \ if (!pDEF_Var.empty()) pNE->ID = pDEF_Var; \ @@ -213,13 +213,13 @@ void X3DImporter::readMetadataSet(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_MetaSet, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_MetaSet, ne); } else { ne = new X3DNodeElementMetaSet(mNodeElementCur); if (!def.empty()) ne->ID = def; ((X3DNodeElementMetaSet *)ne)->Reference = reference; - // also metadata node can contain childs + // also metadata node can contain children if (!isNodeEmpty(node)) childrenReadMetadata(node, ne, "MetadataSet"); else diff --git a/code/AssetLib/X3D/X3DImporter_Networking.cpp b/code/AssetLib/X3D/X3DImporter_Networking.cpp index d5ef58397..f2b471692 100644 --- a/code/AssetLib/X3D/X3DImporter_Networking.cpp +++ b/code/AssetLib/X3D/X3DImporter_Networking.cpp @@ -78,10 +78,9 @@ void X3DImporter::readInline(XmlNode &node) { X3DXmlHelper::getStringListAttribute(node, "url", url); // if "USE" defined then find already defined element. + X3DNodeElementBase *ne = nullptr; if (!use.empty()) { - X3DNodeElementBase *ne; - - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Group, ne); } else { ParseHelper_Group_Begin(true); // create new grouping element and go deeper if node has children. // at this place new group mode created and made current, so we can name it. diff --git a/code/AssetLib/X3D/X3DImporter_Rendering.cpp b/code/AssetLib/X3D/X3DImporter_Rendering.cpp index fe7e68081..66a30a916 100644 --- a/code/AssetLib/X3D/X3DImporter_Rendering.cpp +++ b/code/AssetLib/X3D/X3DImporter_Rendering.cpp @@ -67,7 +67,7 @@ void X3DImporter::readColor(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Color, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Color, ne); } else { // create and if needed - define new geometry object. ne = new X3DNodeElementColor(mNodeElementCur); @@ -99,7 +99,7 @@ void X3DImporter::readColorRGBA(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_ColorRGBA, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_ColorRGBA, ne); } else { // create and if needed - define new geometry object. ne = new X3DNodeElementColorRGBA(mNodeElementCur); @@ -131,7 +131,7 @@ void X3DImporter::readCoordinate(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Coordinate, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Coordinate, ne); } else { // create and if needed - define new geometry object. ne = new X3DNodeElementCoordinate(mNodeElementCur); @@ -174,7 +174,7 @@ void X3DImporter::readIndexedLineSet(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedLineSet, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedLineSet, ne); } else { // check data if ((coordIndex.size() < 2) || ((coordIndex.back() == (-1)) && (coordIndex.size() < 3))) @@ -248,7 +248,7 @@ void X3DImporter::readIndexedTriangleFanSet(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedTriangleFanSet, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedTriangleFanSet, ne); } else { // check data if (index.size() == 0) throw DeadlyImportError("IndexedTriangleFanSet must contain not empty \"index\" attribute."); @@ -354,7 +354,7 @@ void X3DImporter::readIndexedTriangleSet(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedTriangleSet, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedTriangleSet, ne); } else { // check data if (index.size() == 0) throw DeadlyImportError("IndexedTriangleSet must contain not empty \"index\" attribute."); @@ -453,10 +453,12 @@ void X3DImporter::readIndexedTriangleStripSet(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedTriangleStripSet, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_IndexedTriangleStripSet, ne); } else { // check data - if (index.size() == 0) throw DeadlyImportError("IndexedTriangleStripSet must contain not empty \"index\" attribute."); + if (index.empty()) { + throw DeadlyImportError("IndexedTriangleStripSet must contain not empty \"index\" attribute."); + } // create and if needed - define new geometry object. ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_IndexedTriangleStripSet, mNodeElementCur); @@ -544,10 +546,12 @@ void X3DImporter::readLineSet(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_LineSet, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_LineSet, ne); } else { // check data - if (vertexCount.size() == 0) throw DeadlyImportError("LineSet must contain not empty \"vertexCount\" attribute."); + if (vertexCount.empty()) { + throw DeadlyImportError("LineSet must contain not empty \"vertexCount\" attribute."); + } // create and if needed - define new geometry object. ne = new X3DNodeElementSet(X3DElemType::ENET_LineSet, mNodeElementCur); @@ -612,7 +616,7 @@ void X3DImporter::readPointSet(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_PointSet, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_PointSet, ne); } else { // create and if needed - define new geometry object. ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_PointSet, mNodeElementCur); @@ -677,10 +681,12 @@ void X3DImporter::readTriangleFanSet(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TriangleFanSet, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TriangleFanSet, ne); } else { // check data - if (fanCount.size() == 0) throw DeadlyImportError("TriangleFanSet must contain not empty \"fanCount\" attribute."); + if (fanCount.empty()) { + throw DeadlyImportError("TriangleFanSet must contain not empty \"fanCount\" attribute."); + } // create and if needed - define new geometry object. ne = new X3DNodeElementSet(X3DElemType::ENET_TriangleFanSet, mNodeElementCur); @@ -784,7 +790,7 @@ void X3DImporter::readTriangleSet(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TriangleSet, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TriangleSet, ne); } else { // create and if needed - define new geometry object. ne = new X3DNodeElementIndexedSet(X3DElemType::ENET_TriangleSet, mNodeElementCur); @@ -859,7 +865,7 @@ void X3DImporter::readTriangleStripSet(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TriangleStripSet, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TriangleStripSet, ne); } else { // check data if (stripCount.size() == 0) throw DeadlyImportError("TriangleStripSet must contain not empty \"stripCount\" attribute."); @@ -958,14 +964,14 @@ void X3DImporter::readTriangleStripSet(XmlNode &node) { void X3DImporter::readNormal(XmlNode &node) { std::string use, def; std::list vector; - X3DNodeElementBase *ne; + X3DNodeElementBase *ne=nullptr; MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); X3DXmlHelper::getVector3DListAttribute(node, "vector", vector); // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Normal, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Normal, ne); } else { // create and if needed - define new geometry object. ne = new X3DNodeElementNormal(mNodeElementCur); diff --git a/code/AssetLib/X3D/X3DImporter_Shape.cpp b/code/AssetLib/X3D/X3DImporter_Shape.cpp index 7a292be5c..1c472e14b 100644 --- a/code/AssetLib/X3D/X3DImporter_Shape.cpp +++ b/code/AssetLib/X3D/X3DImporter_Shape.cpp @@ -60,7 +60,7 @@ void X3DImporter::readShape(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Shape, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Shape, ne); } else { // create and if needed - define new geometry object. ne = new X3DNodeElementShape(mNodeElementCur); @@ -74,31 +74,55 @@ void X3DImporter::readShape(XmlNode &node) { // check for appearance node if (currentChildName == "Appearance") readAppearance(currentChildNode); // check for X3DGeometryNodes - else if (currentChildName == "Arc2D") readArc2D(currentChildNode); - else if (currentChildName == "ArcClose2D") readArcClose2D(currentChildNode); - else if (currentChildName == "Circle2D") readCircle2D(currentChildNode); - else if (currentChildName == "Disk2D") readDisk2D(currentChildNode); - else if (currentChildName == "Polyline2D") readPolyline2D(currentChildNode); - else if (currentChildName == "Polypoint2D") readPolypoint2D(currentChildNode); - else if (currentChildName == "Rectangle2D") readRectangle2D(currentChildNode); - else if (currentChildName == "TriangleSet2D") readTriangleSet2D(currentChildNode); - else if (currentChildName == "Box") readBox(currentChildNode); - else if (currentChildName == "Cone") readCone(currentChildNode); - else if (currentChildName == "Cylinder") readCylinder(currentChildNode); - else if (currentChildName == "ElevationGrid") readElevationGrid(currentChildNode); - else if (currentChildName == "Extrusion") readExtrusion(currentChildNode); - else if (currentChildName == "IndexedFaceSet") readIndexedFaceSet(currentChildNode); - else if (currentChildName == "Sphere") readSphere(currentChildNode); - else if (currentChildName == "IndexedLineSet") readIndexedLineSet(currentChildNode); - else if (currentChildName == "LineSet") readLineSet(currentChildNode); - else if (currentChildName == "PointSet") readPointSet(currentChildNode); - else if (currentChildName == "IndexedTriangleFanSet") readIndexedTriangleFanSet(currentChildNode); - else if (currentChildName == "IndexedTriangleSet") readIndexedTriangleSet(currentChildNode); - else if (currentChildName == "IndexedTriangleStripSet") readIndexedTriangleStripSet(currentChildNode); - else if (currentChildName == "TriangleFanSet") readTriangleFanSet(currentChildNode); - else if (currentChildName == "TriangleSet") readTriangleSet(currentChildNode); + else if (currentChildName == "Arc2D") + readArc2D(currentChildNode); + else if (currentChildName == "ArcClose2D") + readArcClose2D(currentChildNode); + else if (currentChildName == "Circle2D") + readCircle2D(currentChildNode); + else if (currentChildName == "Disk2D") + readDisk2D(currentChildNode); + else if (currentChildName == "Polyline2D") + readPolyline2D(currentChildNode); + else if (currentChildName == "Polypoint2D") + readPolypoint2D(currentChildNode); + else if (currentChildName == "Rectangle2D") + readRectangle2D(currentChildNode); + else if (currentChildName == "TriangleSet2D") + readTriangleSet2D(currentChildNode); + else if (currentChildName == "Box") + readBox(currentChildNode); + else if (currentChildName == "Cone") + readCone(currentChildNode); + else if (currentChildName == "Cylinder") + readCylinder(currentChildNode); + else if (currentChildName == "ElevationGrid") + readElevationGrid(currentChildNode); + else if (currentChildName == "Extrusion") + readExtrusion(currentChildNode); + else if (currentChildName == "IndexedFaceSet") + readIndexedFaceSet(currentChildNode); + else if (currentChildName == "Sphere") + readSphere(currentChildNode); + else if (currentChildName == "IndexedLineSet") + readIndexedLineSet(currentChildNode); + else if (currentChildName == "LineSet") + readLineSet(currentChildNode); + else if (currentChildName == "PointSet") + readPointSet(currentChildNode); + else if (currentChildName == "IndexedTriangleFanSet") + readIndexedTriangleFanSet(currentChildNode); + else if (currentChildName == "IndexedTriangleSet") + readIndexedTriangleSet(currentChildNode); + else if (currentChildName == "IndexedTriangleStripSet") + readIndexedTriangleStripSet(currentChildNode); + else if (currentChildName == "TriangleFanSet") + readTriangleFanSet(currentChildNode); + else if (currentChildName == "TriangleSet") + readTriangleSet(currentChildNode); // check for X3DMetadataObject - else if (!checkForMetadataNode(currentChildNode)) skipUnsupportedNode("Shape", currentChildNode); + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("Shape", currentChildNode); } ParseHelper_Node_Exit(); @@ -123,42 +147,41 @@ void X3DImporter::readShape(XmlNode &node) { // void X3DImporter::readAppearance(XmlNode &node) { std::string use, def; - X3DNodeElementBase* ne( nullptr ); + X3DNodeElementBase *ne(nullptr); MACRO_ATTRREAD_CHECKUSEDEF_RET(node, def, use); - // if "USE" defined then find already defined element. - if (!use.empty()) - { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Appearance, ne); - } - else - { - // create and if needed - define new geometry object. - ne = new X3DNodeElementAppearance(mNodeElementCur); - if(!def.empty()) ne->ID = def; + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Appearance, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementAppearance(mNodeElementCur); + if (!def.empty()) ne->ID = def; // check for child nodes - if(!isNodeEmpty(node)) - { - ParseHelper_Node_Enter(ne); + if (!isNodeEmpty(node)) { + ParseHelper_Node_Enter(ne); for (auto currentChildNode : node.children()) { const std::string ¤tChildName = currentChildNode.name(); - if (currentChildName == "Material") readMaterial(currentChildNode); - else if (currentChildName == "ImageTexture") readImageTexture(currentChildNode); - else if (currentChildName == "TextureTransform") readTextureTransform(currentChildNode); + if (currentChildName == "Material") + readMaterial(currentChildNode); + else if (currentChildName == "ImageTexture") + readImageTexture(currentChildNode); + else if (currentChildName == "TextureTransform") + readTextureTransform(currentChildNode); // check for X3DMetadataObject - else if (!checkForMetadataNode(currentChildNode)) skipUnsupportedNode("Appearance", currentChildNode); + else if (!checkForMetadataNode(currentChildNode)) + skipUnsupportedNode("Appearance", currentChildNode); } - ParseHelper_Node_Exit(); - }// if(!isNodeEmpty(node)) - else - { - mNodeElementCur->Children.push_back(ne);// add made object as child to current element - } + ParseHelper_Node_Exit(); + } // if(!isNodeEmpty(node)) + else { + mNodeElementCur->Children.push_back(ne); // add made object as child to current element + } - NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph - }// if(!use.empty()) else + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else } // ID = def; + // if "USE" defined then find already defined element. + if (!use.empty()) { + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_Material, ne); + } else { + // create and if needed - define new geometry object. + ne = new X3DNodeElementMaterial(mNodeElementCur); + if (!def.empty()) ne->ID = def; - ((X3DNodeElementMaterial *)ne)->AmbientIntensity = ambientIntensity; + ((X3DNodeElementMaterial *)ne)->AmbientIntensity = ambientIntensity; ((X3DNodeElementMaterial *)ne)->Shininess = shininess; ((X3DNodeElementMaterial *)ne)->Transparency = transparency; ((X3DNodeElementMaterial *)ne)->DiffuseColor = diffuseColor; ((X3DNodeElementMaterial *)ne)->EmissiveColor = emissiveColor; ((X3DNodeElementMaterial *)ne)->SpecularColor = specularColor; // check for child nodes - if(!isNodeEmpty(node)) - childrenReadMetadata(node, ne, "Material"); - else + if (!isNodeEmpty(node)) + childrenReadMetadata(node, ne, "Material"); + else mNodeElementCur->Children.push_back(ne); // add made object as child to current element - NodeElement_List.push_back(ne);// add element to node element list because its a new object in graph - }// if(!use.empty()) else + NodeElement_List.push_back(ne); // add element to node element list because its a new object in graph + } // if(!use.empty()) else } -}// namespace Assimp +} // namespace Assimp #endif // !ASSIMP_BUILD_NO_X3D_IMPORTER diff --git a/code/AssetLib/X3D/X3DImporter_Texturing.cpp b/code/AssetLib/X3D/X3DImporter_Texturing.cpp index 6463e2808..32c1a90d7 100644 --- a/code/AssetLib/X3D/X3DImporter_Texturing.cpp +++ b/code/AssetLib/X3D/X3DImporter_Texturing.cpp @@ -74,7 +74,7 @@ void X3DImporter::readImageTexture(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_ImageTexture, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_ImageTexture, ne); } else { // create and if needed - define new geometry object. ne = new X3DNodeElementImageTexture(mNodeElementCur); @@ -113,7 +113,7 @@ void X3DImporter::readTextureCoordinate(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TextureCoordinate, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TextureCoordinate, ne); } else { // create and if needed - define new geometry object. ne = new X3DNodeElementTextureCoordinate(mNodeElementCur); @@ -154,7 +154,7 @@ void X3DImporter::readTextureTransform(XmlNode &node) { // if "USE" defined then find already defined element. if (!use.empty()) { - MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TextureTransform, ne); + ne = MACRO_USE_CHECKANDAPPLY(node, def, use, ENET_TextureTransform, ne); } else { // create and if needed - define new geometry object. ne = new X3DNodeElementTextureTransform(mNodeElementCur); diff --git a/test/models/X3D/HelloX3dTrademark.x3d b/test/models/X3D/HelloX3dTrademark.x3d new file mode 100644 index 000000000..09aec075e --- /dev/null +++ b/test/models/X3D/HelloX3dTrademark.x3d @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/test.3mf b/test/test.3mf index eb1c49e1b1a6e71d1ce6c95fec699be6d1357051..72f6ae43766ddd789ed0f3fbc56917ccaacf577f 100644 GIT binary patch delta 68 zcmey)@tuP&z?+#xgn@y9gW>(;tl)`!Usyo&W;VtPj9}K}TTHhhjGfH35XLWND+t4a G#U21nG#VuU delta 68 zcmey)@tuP&z?+#xgn@y9gCV(eY0yNzFDxK>GaKUtMlfsgEvDNL#!hBi2;&#C6@+2I GVh;fNrx%3) diff --git a/test/unit/utX3DImportExport.cpp b/test/unit/utX3DImportExport.cpp index de3bfbc96..0d753c889 100644 --- a/test/unit/utX3DImportExport.cpp +++ b/test/unit/utX3DImportExport.cpp @@ -49,7 +49,7 @@ using namespace Assimp; class utX3DImportExport : public AbstractImportExportBase { public: - virtual bool importerTest() { + bool importerTest() override { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/X3D/ComputerKeyboard.x3d", aiProcess_ValidateDataStructure); return nullptr != scene; From a31c979abce1a07a9f0cb61f04d026cd231a0364 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Wed, 10 Nov 2021 20:40:05 +0100 Subject: [PATCH 26/28] Use helloworld as X3D-Testcase --- code/AssetLib/X3D/X3DImporter_Macro.hpp | 15 ++++++--------- test/unit/utX3DImportExport.cpp | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/code/AssetLib/X3D/X3DImporter_Macro.hpp b/code/AssetLib/X3D/X3DImporter_Macro.hpp index 08aac3da7..6d4c61c3f 100644 --- a/code/AssetLib/X3D/X3DImporter_Macro.hpp +++ b/code/AssetLib/X3D/X3DImporter_Macro.hpp @@ -63,15 +63,12 @@ inline X3DNodeElementBase *X3DImporter::MACRO_USE_CHECKANDAPPLY(XmlNode &node, s if (nullptr == mNodeElementCur) { printf("here\n"); } - - //do { - checkNodeMustBeEmpty(node); - if (!pDEF.empty()) - Assimp::Throw_DEF_And_USE(node.name()); - if (!FindNodeElement(pUSE, pType, &pNE)) - Assimp::Throw_USE_NotFound(node.name(), pUSE); - mNodeElementCur->Children.push_back(pNE); /* add found object as child to current element */ - //} while (false); + checkNodeMustBeEmpty(node); + if (!pDEF.empty()) + Assimp::Throw_DEF_And_USE(node.name()); + if (!FindNodeElement(pUSE, pType, &pNE)) + Assimp::Throw_USE_NotFound(node.name(), pUSE); + mNodeElementCur->Children.push_back(pNE); /* add found object as child to current element */ return pNE; } diff --git a/test/unit/utX3DImportExport.cpp b/test/unit/utX3DImportExport.cpp index 0d753c889..a6ad618ab 100644 --- a/test/unit/utX3DImportExport.cpp +++ b/test/unit/utX3DImportExport.cpp @@ -51,7 +51,7 @@ class utX3DImportExport : public AbstractImportExportBase { public: bool importerTest() override { Assimp::Importer importer; - const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/X3D/ComputerKeyboard.x3d", aiProcess_ValidateDataStructure); + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/X3D/HelloX3dTrademark.x3d", aiProcess_ValidateDataStructure); return nullptr != scene; } }; From 952f0a53c995e74dc5c587551523b9ac16122c27 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Thu, 11 Nov 2021 19:06:25 +0100 Subject: [PATCH 27/28] Try to fix a leak --- code/AssetLib/X3D/X3DImporter_Geometry3D.cpp | 3 ++- code/AssetLib/X3D/X3DImporter_Node.hpp | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/code/AssetLib/X3D/X3DImporter_Geometry3D.cpp b/code/AssetLib/X3D/X3DImporter_Geometry3D.cpp index edde34281..2db62dc64 100644 --- a/code/AssetLib/X3D/X3DImporter_Geometry3D.cpp +++ b/code/AssetLib/X3D/X3DImporter_Geometry3D.cpp @@ -897,7 +897,8 @@ void X3DImporter::readSphere(XmlNode &node) { StandardShapes::MakeSphere(tess, tlist); // copy data from temp array and apply scale for (std::vector::iterator it = tlist.begin(); it != tlist.end(); ++it) { - ((X3DNodeElementGeometry3D *)ne)->Vertices.push_back(*it * radius); + aiVector3D v = *it; + ((X3DNodeElementGeometry3D *)ne)->Vertices.emplace_back(v * radius); } ((X3DNodeElementGeometry3D *)ne)->Solid = solid; diff --git a/code/AssetLib/X3D/X3DImporter_Node.hpp b/code/AssetLib/X3D/X3DImporter_Node.hpp index 95b8735d4..8d33c4b7a 100644 --- a/code/AssetLib/X3D/X3DImporter_Node.hpp +++ b/code/AssetLib/X3D/X3DImporter_Node.hpp @@ -108,6 +108,10 @@ struct X3DNodeElementBase { std::list Children; X3DElemType Type; + virtual ~X3DNodeElementBase() { + // empty + } + protected: X3DNodeElementBase(X3DElemType type, X3DNodeElementBase *pParent) : Parent(pParent), Type(type) { From 97b8e41997160f2113e1e6fcde230ccb857167af Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Fri, 12 Nov 2021 09:56:45 +0100 Subject: [PATCH 28/28] Fix formatting --- code/Common/RemoveComments.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/code/Common/RemoveComments.cpp b/code/Common/RemoveComments.cpp index 2de9666de..e9e2a6ade 100644 --- a/code/Common/RemoveComments.cpp +++ b/code/Common/RemoveComments.cpp @@ -4,7 +4,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2021, assimp team - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -40,20 +39,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** @file RemoveComments.cpp +/** + * @file RemoveComments.cpp * @brief Defines the CommentRemover utility class */ #include #include -namespace Assimp { +namespace Assimp { // ------------------------------------------------------------------------------------------------ // Remove line comments from a file -void CommentRemover::RemoveLineComments(const char* szComment, - char* szBuffer, char chReplacement /* = ' ' */) -{ +void CommentRemover::RemoveLineComments(const char* szComment, char* szBuffer, char chReplacement /* = ' ' */) { // validate parameters ai_assert(nullptr != szComment); ai_assert(nullptr != szBuffer); @@ -75,8 +73,9 @@ void CommentRemover::RemoveLineComments(const char* szComment, } if (!strncmp(szBuffer + i,szComment,len)) { - while (i < lenBuffer && !IsLineEnd(szBuffer[i])) + while (i < lenBuffer && !IsLineEnd(szBuffer[i])) { szBuffer[i++] = chReplacement; + } } } } @@ -84,9 +83,8 @@ void CommentRemover::RemoveLineComments(const char* szComment, // ------------------------------------------------------------------------------------------------ // Remove multi-line comments from a file void CommentRemover::RemoveMultiLineComments(const char* szCommentStart, - const char* szCommentEnd,char* szBuffer, - char chReplacement) -{ + const char* szCommentEnd,char* szBuffer, + char chReplacement) { // validate parameters ai_assert(nullptr != szCommentStart); ai_assert(nullptr != szCommentEnd); @@ -99,18 +97,20 @@ void CommentRemover::RemoveMultiLineComments(const char* szCommentStart, while (*szBuffer) { // skip over quotes - if (*szBuffer == '\"' || *szBuffer == '\'') + if (*szBuffer == '\"' || *szBuffer == '\'') { while (*szBuffer++ && *szBuffer != '\"' && *szBuffer != '\''); + } if (!strncmp(szBuffer,szCommentStart,len2)) { while (*szBuffer) { if (!::strncmp(szBuffer,szCommentEnd,len)) { - for (unsigned int i = 0; i < len;++i) + for (unsigned int i = 0; i < len;++i) { *szBuffer++ = chReplacement; + } break; } - *szBuffer++ = chReplacement; + *szBuffer++ = chReplacement; } continue; }

    Ao$|V)IS!XrjDofR+cp% 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