void PTexMeshData::parsePLY()

in src/esp/assets/PTexMeshData.cpp [510:820]


void PTexMeshData::parsePLY(const std::string& filename,
                            PTexMeshData::MeshData& meshData) {
  std::vector<std::string> comments;
  std::vector<std::string> objInfo;

  std::string lastElement;
  std::string lastProperty;

  enum Properties { POSITION = 0, NORMAL, COLOR, NUM_PROPERTIES };

  size_t numVertices = 0;

  size_t positionDimensions = 0;
  size_t normalDimensions = 0;
  size_t colorDimensions = 0;

  std::vector<Properties> vertexLayout;

  size_t numFaces = 0;

  std::ifstream file(filename, std::ios::binary);

  // Header parsing
  {
    std::string line;

    while (std::getline(file, line)) {
      std::istringstream ls(line);
      std::string token;
      ls >> token;

      if (token == "ply" || token == "PLY" || token == "") {
        // Skip preamble line
        continue;
      } else if (token == "comment") {
        // Just store these incase
        comments.push_back(line.erase(0, 8));
      } else if (token == "format") {
        // We can only parse binary data, so check that's what it is
        std::string s;
        ls >> s;
        CORRADE_ASSERT(s == "binary_little_endian",
                       "PTexMeshData::parsePLY: the file is not a binary file "
                       "in little endian byte order", );
      } else if (token == "element") {
        std::string name;
        size_t size = 0;
        ls >> name >> size;

        if (name == "vertex") {
          // Pull out the number of vertices
          numVertices = size;
        } else if (name == "face") {
          // Pull out number of faces
          numFaces = size;
          CORRADE_ASSERT(numFaces > 0,
                         "PTexMeshData::parsePLY: number of faces is not "
                         "greater than 0.", );
        } else {
          CORRADE_ASSERT(
              false, "PTexMeshData::parsePLY: Cannot parse element" << name, );
        }

        // Keep track of what element we parsed last to associate the
        // properties that follow
        lastElement = name;
      } else if (token == "property") {
        std::string type, name;
        ls >> type;

        // Special parsing for list properties (e.g. faces)
        bool isList = false;

        if (type == "list") {
          isList = true;

          std::string countType;
          ls >> countType >> type;

          CORRADE_ASSERT(countType == "uchar" || countType == "uint8",
                         "PTexMeshData::parsePLY: Don't understand count type"
                             << countType, );

          CORRADE_ASSERT(
              type == "int",
              "PTexMeshData::parsePLY: Don't understand index type" << type, );

          CORRADE_ASSERT(lastElement == "face",
                         "PTexMeshData::parsePLY: Only expecting list after "
                         "face element, not after"
                             << lastElement, );
        }

        CORRADE_ASSERT(
            type == "float" || type == "int" || type == "uchar" ||
                type == "uint8",
            "PTexMeshData::parsePLY: Don't understand type" << type, );

        ls >> name;

        // Collecting vertex property information
        if (lastElement == "vertex") {
          CORRADE_ASSERT(type != "int",
                         "PTexMeshData::parsePLY: Don't support 32-bit integer "
                         "properties", );

          // Position information
          if (name == "x") {
            positionDimensions = 1;
            vertexLayout.push_back(Properties::POSITION);
            CORRADE_ASSERT(type == "float",
                           "PTexMeshData::parsePLY: Don't support 8-bit "
                           "integer positions", );
          } else if (name == "y") {
            CORRADE_ASSERT(lastProperty == "x",
                           "PTexMeshData::parsePLY: Properties should follow "
                           "x, y, z, (w) order", );
            positionDimensions = 2;
          } else if (name == "z") {
            CORRADE_ASSERT(lastProperty == "y",
                           "PTexMeshData::parsePLY: Properties should follow "
                           "x, y, z, (w) order", );
            positionDimensions = 3;
          } else if (name == "w") {
            CORRADE_ASSERT(lastProperty == "z",
                           "PTexMeshData::parsePLY: Properties should follow "
                           "x, y, z, (w) order", );
            positionDimensions = 4;
          }

          // Normal information
          if (name == "nx") {
            normalDimensions = 1;
            vertexLayout.push_back(Properties::NORMAL);
            CORRADE_ASSERT(type == "float",
                           "PTexMeshData::parsePLY: Don't support 8-bit "
                           "integer normals", );
          } else if (name == "ny") {
            CORRADE_ASSERT(lastProperty == "nx",
                           "PTexMeshData::parsePLY: Properties should follow "
                           "nx, ny, nz order", );
            normalDimensions = 2;
          } else if (name == "nz") {
            CORRADE_ASSERT(lastProperty == "ny",
                           "PTexMeshData::parsePLY: Properties should follow "
                           "nx, ny, nz order", );
            normalDimensions = 3;
          }

          // Color information
          if (name == "red") {
            colorDimensions = 1;
            vertexLayout.push_back(Properties::COLOR);
            CORRADE_ASSERT(type == "uchar" || type == "uint8",
                           "PTexMeshData::parsePLY: Don't support non-8-bit "
                           "integer colors", );
          } else if (name == "green") {
            CORRADE_ASSERT(lastProperty == "red",
                           "PTexMeshData::parsePLY: Properties should follow "
                           "red, green, blue, (alpha) order", );
            colorDimensions = 2;
          } else if (name == "blue") {
            CORRADE_ASSERT(lastProperty == "green",
                           "PTexMeshData::parsePLY: Properties should follow "
                           "red, green, blue, (alpha) order", );
            colorDimensions = 3;
          } else if (name == "alpha") {
            CORRADE_ASSERT(lastProperty == "blue",
                           "PTexMeshData::parsePLY: Properties should follow "
                           "red, green, blue, (alpha) order", );
            colorDimensions = 4;
          }
        } else if (lastElement == "face") {
          CORRADE_ASSERT(isList,
                         "PTexMeshData::parsePLY: No idea what to do with "
                         "properties following faces", );
        } else {
          // No idea what to do with properties before elements
          CORRADE_INTERNAL_ASSERT_UNREACHABLE();
        }

        lastProperty = name;
      } else if (token == "obj_info") {
        // Just store these incase
        objInfo.push_back(line.erase(0, 9));
      } else if (token == "end_header") {
        // Done reading!
        break;
      } else {
        // Something unrecognised
        CORRADE_INTERNAL_ASSERT_UNREACHABLE();
      }
    }

    // Check things make sense.
    CORRADE_ASSERT(
        numVertices > 0,
        "PTexMeshData::parsePLY: number of vertices is not greater than 0", );
    CORRADE_ASSERT(positionDimensions > 0,
                   "PTexMeshData::parsePLY: the dimensions of the position is "
                   "not greater than 0", );
    CORRADE_ASSERT(
        positionDimensions == 3,
        "PTexMeshData::parsePLY: the dimensions of the position must be 3.", );
  }

  meshData.vbo.resize(numVertices, vec3f(0, 0, 0));

  if (normalDimensions != 0u) {
    meshData.nbo.resize(numVertices, vec4f(0, 0, 0, 1));
  }

  if (colorDimensions != 0u) {
    meshData.cbo.resize(numVertices, vec4uc(0, 0, 0, 255));
  }

  // Can only be FLOAT32 or UINT8
  const size_t positionBytes = positionDimensions * sizeof(float);  // floats
  const size_t normalBytes = normalDimensions * sizeof(float);      // floats
  const size_t colorBytes = colorDimensions * sizeof(uint8_t);      // bytes

  const size_t vertexPacketSizeBytes = positionBytes + normalBytes + colorBytes;

  size_t positionOffsetBytes = 0;
  size_t normalOffsetBytes = 0;
  size_t colorOffsetBytes = 0;

  size_t offsetSoFarBytes = 0;

  for (size_t i = 0; i < vertexLayout.size(); i++) {
    if (vertexLayout[i] == Properties::POSITION) {
      positionOffsetBytes = offsetSoFarBytes;
      offsetSoFarBytes += positionBytes;
    } else if (vertexLayout[i] == Properties::NORMAL) {
      normalOffsetBytes = offsetSoFarBytes;
      offsetSoFarBytes += normalBytes;
    } else if (vertexLayout[i] == Properties::COLOR) {
      colorOffsetBytes = offsetSoFarBytes;
      offsetSoFarBytes += colorBytes;
    } else {
      CORRADE_INTERNAL_ASSERT_UNREACHABLE();
    }
  }

  // Close after parsing header and re-open memory mapped
  const size_t postHeader = file.tellg();

  file.close();

  Cr::Containers::Array<const char, Cr::Utility::Directory::MapDeleter>
      mmappedData = Cr::Utility::Directory::mapRead(filename);

  const size_t fileSize = *Cr::Utility::Directory::fileSize(filename);

  // Parse each vertex packet and unpack
  const char* bytes = mmappedData + postHeader;

  for (size_t i = 0; i < numVertices; i++) {
    const char* nextBytes = bytes + vertexPacketSizeBytes * i;

    memcpy(meshData.vbo[i].data(), &nextBytes[positionOffsetBytes],
           positionBytes);

    if (normalDimensions != 0u)
      memcpy(meshData.nbo[i].data(), &nextBytes[normalOffsetBytes],
             normalBytes);

    if (colorDimensions != 0u)
      memcpy(meshData.cbo[i].data(), &nextBytes[colorOffsetBytes], colorBytes);
  }

  const size_t bytesSoFar = postHeader + vertexPacketSizeBytes * numVertices;

  bytes = mmappedData + postHeader + vertexPacketSizeBytes * numVertices;

  // Read first face to get number of indices;
  const uint8_t faceDimensions = *bytes;

  CORRADE_ASSERT(faceDimensions == 3 || faceDimensions == 4,
                 "PTexMeshData::parsePLY: the dimension of a face is neither "
                 "3 nor 4.", );

  const size_t countBytes = 1;
  const size_t faceBytes = faceDimensions * sizeof(uint32_t);  // uint32_t
  const size_t facePacketSizeBytes = countBytes + faceBytes;

  const size_t predictedFaces = (fileSize - bytesSoFar) / facePacketSizeBytes;

  // Not sure what to do here
  //    if(predictedFaces < numFaces)
  //    {
  //        ESP_DEBUG()  << "Skipping" << numFaces - predictedFaces  << "missing
  //        faces" << std::endl;
  //    }
  //    else if(numFaces < predictedFaces)
  //    {
  //        ESP_DEBUG()  << "Ignoring" << predictedFaces - numFaces  << "extra
  //        faces" << std::endl;
  //    }

  numFaces = std::min(numFaces, predictedFaces);

  meshData.ibo.resize(numFaces * faceDimensions);

  for (size_t i = 0; i < numFaces; i++) {
    const char* nextBytes = bytes + facePacketSizeBytes * i;

    memcpy(&meshData.ibo[i * faceDimensions], &nextBytes[countBytes],
           faceBytes);
  }
}