9331803efc
- aggiunta importazione formato PLY - migliorata importazione formato OFF.
2059 lines
59 KiB
C++
2059 lines
59 KiB
C++
/*
|
|
MIT License
|
|
|
|
Copyright (c) 2019 Vilya Harvey
|
|
|
|
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.
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "miniply.h"
|
|
|
|
#include <cassert>
|
|
#include <cctype>
|
|
#include <cmath>
|
|
#include <cstdarg>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
#ifndef _WIN32
|
|
#include <errno.h>
|
|
#endif
|
|
|
|
|
|
namespace miniply {
|
|
|
|
//
|
|
// Public constants
|
|
//
|
|
|
|
// Standard PLY element names
|
|
const char* kPLYVertexElement = "vertex";
|
|
const char* kPLYFaceElement = "face";
|
|
|
|
|
|
//
|
|
// PLY constants
|
|
//
|
|
|
|
static constexpr uint32_t kPLYReadBufferSize = 128 * 1024;
|
|
static constexpr uint32_t kPLYTempBufferSize = kPLYReadBufferSize;
|
|
|
|
static const char* kPLYFileTypes[] = { "ascii", "binary_little_endian", "binary_big_endian", nullptr };
|
|
static const uint32_t kPLYPropertySize[]= { 1, 1, 2, 2, 4, 4, 4, 8 };
|
|
|
|
struct PLYTypeAlias {
|
|
const char* name;
|
|
PLYPropertyType type;
|
|
};
|
|
|
|
static const PLYTypeAlias kTypeAliases[] = {
|
|
{ "char", PLYPropertyType::Char },
|
|
{ "uchar", PLYPropertyType::UChar },
|
|
{ "short", PLYPropertyType::Short },
|
|
{ "ushort", PLYPropertyType::UShort },
|
|
{ "int", PLYPropertyType::Int },
|
|
{ "uint", PLYPropertyType::UInt },
|
|
{ "float", PLYPropertyType::Float },
|
|
{ "float32",PLYPropertyType::Float },
|
|
{ "float64",PLYPropertyType::Double },
|
|
{ "double", PLYPropertyType::Double },
|
|
|
|
{ "uint8", PLYPropertyType::UChar },
|
|
{ "uint16", PLYPropertyType::UShort },
|
|
{ "uint32", PLYPropertyType::UInt },
|
|
|
|
{ "int8", PLYPropertyType::Char },
|
|
{ "int16", PLYPropertyType::Short },
|
|
{ "int32", PLYPropertyType::Int },
|
|
|
|
{ nullptr, PLYPropertyType::None }
|
|
};
|
|
|
|
|
|
//
|
|
// Constants
|
|
//
|
|
|
|
static constexpr double kDoubleDigits[10] = { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 };
|
|
|
|
static constexpr float kPi = 3.14159265358979323846f;
|
|
|
|
|
|
//
|
|
// Vec2 type
|
|
//
|
|
|
|
struct Vec2 {
|
|
float x, y;
|
|
};
|
|
|
|
static inline Vec2 operator - (Vec2 lhs, Vec2 rhs) { return Vec2{ lhs.x - rhs.x, lhs.y - rhs.y }; }
|
|
|
|
static inline float dot(Vec2 lhs, Vec2 rhs) { return lhs.x * rhs.x + lhs.y * rhs.y; }
|
|
static inline float length(Vec2 v) { return std::sqrt(dot(v, v)); }
|
|
static inline Vec2 normalize(Vec2 v) { float len = length(v); return Vec2{ v.x / len, v.y / len }; }
|
|
|
|
|
|
//
|
|
// Vec3 type
|
|
//
|
|
|
|
struct Vec3 {
|
|
float x, y, z;
|
|
};
|
|
|
|
static inline Vec3 operator - (Vec3 lhs, Vec3 rhs) { return Vec3{ lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z }; }
|
|
|
|
static inline float dot(Vec3 lhs, Vec3 rhs) { return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z; }
|
|
static inline float length(Vec3 v) { return std::sqrt(dot(v, v)); }
|
|
static inline Vec3 normalize(Vec3 v) { float len = length(v); return Vec3{ v.x / len, v.y / len, v.z / len }; }
|
|
static inline Vec3 cross(Vec3 lhs, Vec3 rhs) { return Vec3{ lhs.y * rhs.z - lhs.z * rhs.y, lhs.z * rhs.x - lhs.x * rhs.z, lhs.x * rhs.y - lhs.y * rhs.x }; }
|
|
|
|
|
|
//
|
|
// Internal-only functions
|
|
//
|
|
|
|
static inline bool is_whitespace(char ch)
|
|
{
|
|
return ch == ' ' || ch == '\t' || ch == '\r';
|
|
}
|
|
|
|
|
|
static inline bool is_digit(char ch)
|
|
{
|
|
return ch >= '0' && ch <= '9';
|
|
}
|
|
|
|
|
|
static inline bool is_letter(char ch)
|
|
{
|
|
ch |= 32; // upper and lower case letters differ only at this bit.
|
|
return ch >= 'a' && ch <= 'z';
|
|
}
|
|
|
|
|
|
static inline bool is_alnum(char ch)
|
|
{
|
|
return is_digit(ch) || is_letter(ch);
|
|
}
|
|
|
|
|
|
static inline bool is_keyword_start(char ch)
|
|
{
|
|
return is_letter(ch) || ch == '_';
|
|
}
|
|
|
|
|
|
static inline bool is_keyword_part(char ch)
|
|
{
|
|
return is_alnum(ch) || ch == '_';
|
|
}
|
|
|
|
|
|
static inline bool is_safe_buffer_end(char ch)
|
|
{
|
|
return (ch > 0 && ch <= 32) || (ch >= 127);
|
|
}
|
|
|
|
|
|
static int file_open(FILE** f, const char* filename, const char* mode)
|
|
{
|
|
#ifdef _WIN32
|
|
return fopen_s(f, filename, mode);
|
|
#else
|
|
*f = fopen(filename, mode);
|
|
return (*f != nullptr) ? 0 : errno;
|
|
#endif
|
|
}
|
|
|
|
|
|
static inline int file_seek(FILE* file, int64_t offset, int origin)
|
|
{
|
|
#ifdef _WIN32
|
|
return _fseeki64(file, offset, origin);
|
|
#else
|
|
static_assert(sizeof(off_t) == sizeof(int64_t), "off_t is not 64 bits.");
|
|
return fseeko(file, offset, origin);
|
|
#endif
|
|
}
|
|
|
|
|
|
static bool int_literal(const char* start, char const** end, int* val)
|
|
{
|
|
const char* pos = start;
|
|
|
|
bool negative = false;
|
|
if (*pos == '-') {
|
|
negative = true;
|
|
++pos;
|
|
}
|
|
else if (*pos == '+') {
|
|
++pos;
|
|
}
|
|
|
|
bool hasLeadingZeroes = *pos == '0';
|
|
if (hasLeadingZeroes) {
|
|
do {
|
|
++pos;
|
|
} while (*pos == '0');
|
|
}
|
|
|
|
int numDigits = 0;
|
|
int localVal = 0;
|
|
while (is_digit(*pos)) {
|
|
// FIXME: this will overflow if we get too many digits.
|
|
localVal = localVal * 10 + static_cast<int>(*pos - '0');
|
|
++numDigits;
|
|
++pos;
|
|
}
|
|
|
|
if (numDigits == 0 && hasLeadingZeroes) {
|
|
numDigits = 1;
|
|
}
|
|
|
|
if (numDigits == 0 || is_letter(*pos) || *pos == '_') {
|
|
return false;
|
|
}
|
|
else if (numDigits > 10) {
|
|
// Overflow, literal value is larger than an int can hold.
|
|
// FIXME: this won't catch *all* cases of overflow, make it exact.
|
|
return false;
|
|
}
|
|
|
|
if (val != nullptr) {
|
|
*val = negative ? -localVal : localVal;
|
|
}
|
|
if (end != nullptr) {
|
|
*end = pos;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool double_literal(const char* start, char const** end, double* val)
|
|
{
|
|
const char* pos = start;
|
|
|
|
bool negative = false;
|
|
if (*pos == '-') {
|
|
negative = true;
|
|
++pos;
|
|
}
|
|
else if (*pos == '+') {
|
|
++pos;
|
|
}
|
|
|
|
double localVal = 0.0;
|
|
|
|
bool hasIntDigits = is_digit(*pos);
|
|
if (hasIntDigits) {
|
|
do {
|
|
localVal = localVal * 10.0 + kDoubleDigits[*pos - '0'];
|
|
++pos;
|
|
} while (is_digit(*pos));
|
|
}
|
|
else if (*pos != '.') {
|
|
// set_error("Not a floating point number");
|
|
return false;
|
|
}
|
|
|
|
bool hasFracDigits = false;
|
|
if (*pos == '.') {
|
|
++pos;
|
|
hasFracDigits = is_digit(*pos);
|
|
if (hasFracDigits) {
|
|
double scale = 0.1;
|
|
do {
|
|
localVal += scale * kDoubleDigits[*pos - '0'];
|
|
scale *= 0.1;
|
|
++pos;
|
|
} while (is_digit(*pos));
|
|
}
|
|
else if (!hasIntDigits) {
|
|
// set_error("Floating point number has no digits before or after the decimal point");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool hasExponent = *pos == 'e' || *pos == 'E';
|
|
if (hasExponent) {
|
|
++pos;
|
|
bool negativeExponent = false;
|
|
if (*pos == '-') {
|
|
negativeExponent = true;
|
|
++pos;
|
|
}
|
|
else if (*pos == '+') {
|
|
++pos;
|
|
}
|
|
|
|
if (!is_digit(*pos)) {
|
|
// set_error("Floating point exponent has no digits");
|
|
return false; // error: exponent part has no digits.
|
|
}
|
|
|
|
double exponent = 0.0;
|
|
do {
|
|
exponent = exponent * 10.0 + kDoubleDigits[*pos - '0'];
|
|
++pos;
|
|
} while (is_digit(*pos));
|
|
|
|
if (val != nullptr) {
|
|
if (negativeExponent) {
|
|
exponent = -exponent;
|
|
}
|
|
localVal *= std::pow(10.0, exponent);
|
|
}
|
|
}
|
|
|
|
if (*pos == '.' || *pos == '_' || is_alnum(*pos)) {
|
|
// set_error("Floating point number has trailing chars");
|
|
return false;
|
|
}
|
|
|
|
if (negative) {
|
|
localVal = -localVal;
|
|
}
|
|
|
|
if (val != nullptr) {
|
|
*val = localVal;
|
|
}
|
|
if (end != nullptr) {
|
|
*end = pos;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool float_literal(const char* start, char const** end, float* val)
|
|
{
|
|
double tmp = 0.0;
|
|
bool ok = double_literal(start, end, &tmp);
|
|
if (ok && val != nullptr) {
|
|
*val = static_cast<float>(tmp);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
|
|
static inline void endian_swap_2(uint8_t* data)
|
|
{
|
|
uint16_t tmp = *reinterpret_cast<uint16_t*>(data);
|
|
tmp = static_cast<uint16_t>((tmp >> 8) | (tmp << 8));
|
|
*reinterpret_cast<uint16_t*>(data) = tmp;
|
|
}
|
|
|
|
|
|
static inline void endian_swap_4(uint8_t* data)
|
|
{
|
|
uint32_t tmp = *reinterpret_cast<uint32_t*>(data);
|
|
tmp = (tmp >> 16) | (tmp << 16);
|
|
tmp = ((tmp & 0xFF00FF00) >> 8) | ((tmp & 0x00FF00FF) << 8);
|
|
*reinterpret_cast<uint32_t*>(data) = tmp;
|
|
}
|
|
|
|
|
|
static inline void endian_swap_8(uint8_t* data)
|
|
{
|
|
uint64_t tmp = *reinterpret_cast<uint64_t*>(data);
|
|
tmp = (tmp >> 32) | (tmp << 32);
|
|
tmp = ((tmp & 0xFFFF0000FFFF0000) >> 16) | ((tmp & 0x0000FFFF0000FFFF) << 16);
|
|
tmp = ((tmp & 0xFF00FF00FF00FF00) >> 8) | ((tmp & 0x00FF00FF00FF00FF) << 8);
|
|
*reinterpret_cast<uint64_t*>(data) = tmp;
|
|
}
|
|
|
|
|
|
static inline void endian_swap(uint8_t* data, PLYPropertyType type)
|
|
{
|
|
switch (kPLYPropertySize[uint32_t(type)]) {
|
|
case 2: endian_swap_2(data); break;
|
|
case 4: endian_swap_4(data); break;
|
|
case 8: endian_swap_8(data); break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
|
|
static inline void endian_swap_array(uint8_t* data, PLYPropertyType type, int n)
|
|
{
|
|
switch (kPLYPropertySize[uint32_t(type)]) {
|
|
case 2:
|
|
for (const uint8_t* end = data + 2 * n; data < end; data += 2) {
|
|
endian_swap_2(data);
|
|
}
|
|
break;
|
|
case 4:
|
|
for (const uint8_t* end = data + 4 * n; data < end; data += 4) {
|
|
endian_swap_4(data);
|
|
}
|
|
break;
|
|
case 8:
|
|
for (const uint8_t* end = data + 8 * n; data < end; data += 8) {
|
|
endian_swap_8(data);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
template <class T>
|
|
static void copy_and_convert_to(T* dest, const uint8_t* src, PLYPropertyType srcType)
|
|
{
|
|
switch (srcType) {
|
|
case PLYPropertyType::Char: *dest = static_cast<T>(*reinterpret_cast<const int8_t*>(src)); break;
|
|
case PLYPropertyType::UChar: *dest = static_cast<T>(*reinterpret_cast<const uint8_t*>(src)); break;
|
|
case PLYPropertyType::Short: *dest = static_cast<T>(*reinterpret_cast<const int16_t*>(src)); break;
|
|
case PLYPropertyType::UShort: *dest = static_cast<T>(*reinterpret_cast<const uint16_t*>(src)); break;
|
|
case PLYPropertyType::Int: *dest = static_cast<T>(*reinterpret_cast<const int*>(src)); break;
|
|
case PLYPropertyType::UInt: *dest = static_cast<T>(*reinterpret_cast<const uint32_t*>(src)); break;
|
|
case PLYPropertyType::Float: *dest = static_cast<T>(*reinterpret_cast<const float*>(src)); break;
|
|
case PLYPropertyType::Double: *dest = static_cast<T>(*reinterpret_cast<const double*>(src)); break;
|
|
case PLYPropertyType::None: break;
|
|
}
|
|
}
|
|
|
|
|
|
static void copy_and_convert(uint8_t* dest, PLYPropertyType destType, const uint8_t* src, PLYPropertyType srcType)
|
|
{
|
|
switch (destType) {
|
|
case PLYPropertyType::Char: copy_and_convert_to(reinterpret_cast<int8_t*> (dest), src, srcType); break;
|
|
case PLYPropertyType::UChar: copy_and_convert_to(reinterpret_cast<uint8_t*> (dest), src, srcType); break;
|
|
case PLYPropertyType::Short: copy_and_convert_to(reinterpret_cast<int16_t*> (dest), src, srcType); break;
|
|
case PLYPropertyType::UShort: copy_and_convert_to(reinterpret_cast<uint16_t*>(dest), src, srcType); break;
|
|
case PLYPropertyType::Int: copy_and_convert_to(reinterpret_cast<int32_t*> (dest), src, srcType); break;
|
|
case PLYPropertyType::UInt: copy_and_convert_to(reinterpret_cast<uint32_t*>(dest), src, srcType); break;
|
|
case PLYPropertyType::Float: copy_and_convert_to(reinterpret_cast<float*> (dest), src, srcType); break;
|
|
case PLYPropertyType::Double: copy_and_convert_to(reinterpret_cast<double*> (dest), src, srcType); break;
|
|
case PLYPropertyType::None: break;
|
|
}
|
|
}
|
|
|
|
|
|
static inline bool compatible_types(PLYPropertyType srcType, PLYPropertyType destType)
|
|
{
|
|
return (srcType == destType) ||
|
|
(srcType < PLYPropertyType::Float && (uint32_t(srcType) ^ 0x1) == uint32_t(destType));
|
|
}
|
|
|
|
|
|
//
|
|
// PLYElement methods
|
|
//
|
|
|
|
void PLYElement::calculate_offsets()
|
|
{
|
|
fixedSize = true;
|
|
for (PLYProperty& prop : properties) {
|
|
if (prop.countType != PLYPropertyType::None) {
|
|
fixedSize = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Note that each list property gets its own separate storage. Only fixed
|
|
// size properties go into the common data block. The `rowStride` is the
|
|
// size of a row in the common data block.
|
|
rowStride = 0;
|
|
for (PLYProperty& prop : properties) {
|
|
if (prop.countType != PLYPropertyType::None) {
|
|
continue;
|
|
}
|
|
prop.offset = rowStride;
|
|
rowStride += kPLYPropertySize[uint32_t(prop.type)];
|
|
}
|
|
}
|
|
|
|
|
|
uint32_t PLYElement::find_property(const char *propName) const
|
|
{
|
|
for (uint32_t i = 0, endI = uint32_t(properties.size()); i < endI; i++) {
|
|
if (strcmp(propName, properties.at(i).name.c_str()) == 0) {
|
|
return i;
|
|
}
|
|
}
|
|
return kInvalidIndex;
|
|
}
|
|
|
|
|
|
bool PLYElement::find_properties(uint32_t propIdxs[], uint32_t numIdxs, ...) const
|
|
{
|
|
va_list args;
|
|
va_start(args, numIdxs);
|
|
bool foundAll = find_properties_va(propIdxs, numIdxs, args);
|
|
va_end(args);
|
|
return foundAll;
|
|
}
|
|
|
|
|
|
bool PLYElement::find_properties_va(uint32_t propIdxs[], uint32_t numIdxs, va_list names) const
|
|
{
|
|
for (uint32_t i = 0; i < numIdxs; i++) {
|
|
propIdxs[i] = find_property(va_arg(names, const char*));
|
|
if (propIdxs[i] == kInvalidIndex) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYElement::convert_list_to_fixed_size(uint32_t listPropIdx, uint32_t listSize, uint32_t newPropIdxs[])
|
|
{
|
|
if (fixedSize || listPropIdx >= properties.size() || properties[listPropIdx].countType == PLYPropertyType::None) {
|
|
return false;
|
|
}
|
|
|
|
PLYProperty oldListProp = properties[listPropIdx];
|
|
|
|
// If the generated names are less than 256 chars, we will use an array on
|
|
// the stack as temporary storage. In the rare case that they're longer,
|
|
// we'll allocate an array of sufficient size on the heap and use that
|
|
// instead. This means we'll avoid allocating in all but the most extreme
|
|
// cases.
|
|
char inlineBuf[256];
|
|
size_t nameBufSize = oldListProp.name.size() + 12; // the +12 allows space for an '_', a number up to 10 digits long and the terminating null.
|
|
char* nameBuf = inlineBuf;
|
|
if (nameBufSize > sizeof(inlineBuf)) {
|
|
nameBuf = new char[nameBufSize];
|
|
}
|
|
|
|
// Set up a property for the list count column.
|
|
PLYProperty& countProp = properties[listPropIdx];
|
|
snprintf(nameBuf, nameBufSize, "%s_count", oldListProp.name.c_str());
|
|
countProp.name = nameBuf;
|
|
countProp.type = oldListProp.countType;
|
|
countProp.countType = PLYPropertyType::None;
|
|
countProp.stride = kPLYPropertySize[uint32_t(oldListProp.countType)];
|
|
|
|
if (listSize > 0) {
|
|
// Set up additional properties for the list entries, 1 per entry.
|
|
if (listPropIdx + 1 == properties.size()) {
|
|
properties.resize(properties.size() + listSize);
|
|
}
|
|
else {
|
|
properties.insert(properties.begin() + listPropIdx + 1, listSize, PLYProperty());
|
|
}
|
|
|
|
for (uint32_t i = 0; i < listSize; i++) {
|
|
uint32_t propIdx = listPropIdx + 1 + i;
|
|
|
|
PLYProperty& itemProp = properties[propIdx];
|
|
snprintf(nameBuf, nameBufSize, "%s_%u", oldListProp.name.c_str(), i);
|
|
itemProp.name = nameBuf;
|
|
itemProp.type = oldListProp.type;
|
|
itemProp.countType = PLYPropertyType::None;
|
|
itemProp.stride = kPLYPropertySize[uint32_t(oldListProp.type)];
|
|
|
|
newPropIdxs[i] = propIdx;
|
|
}
|
|
}
|
|
|
|
if (nameBuf != inlineBuf) {
|
|
delete[] nameBuf;
|
|
}
|
|
|
|
calculate_offsets();
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// PLYReader methods
|
|
//
|
|
|
|
PLYReader::PLYReader(const char* filename)
|
|
{
|
|
m_buf = new char[kPLYReadBufferSize + 1];
|
|
m_buf[kPLYReadBufferSize] = '\0';
|
|
|
|
m_tmpBuf = new char[kPLYTempBufferSize + 1];
|
|
m_tmpBuf[kPLYTempBufferSize] = '\0';
|
|
|
|
m_bufEnd = m_buf + kPLYReadBufferSize;
|
|
m_pos = m_bufEnd;
|
|
m_end = m_bufEnd;
|
|
|
|
if (file_open(&m_f, filename, "rb") != 0) {
|
|
m_f = nullptr;
|
|
m_valid = false;
|
|
return;
|
|
}
|
|
m_valid = true;
|
|
|
|
refill_buffer();
|
|
|
|
m_valid = keyword("ply") && next_line() &&
|
|
keyword("format") && advance() &&
|
|
typed_which(kPLYFileTypes, &m_fileType) && advance() &&
|
|
int_literal(&m_majorVersion) && advance() &&
|
|
match(".") && advance() &&
|
|
int_literal(&m_minorVersion) && next_line() &&
|
|
parse_elements() &&
|
|
keyword("end_header") && advance() && match("\n") && accept();
|
|
if (!m_valid) {
|
|
return;
|
|
}
|
|
m_inDataSection = true;
|
|
if (m_fileType == PLYFileType::ASCII) {
|
|
advance();
|
|
}
|
|
|
|
for (PLYElement& elem : m_elements) {
|
|
elem.calculate_offsets();
|
|
}
|
|
}
|
|
|
|
|
|
PLYReader::~PLYReader()
|
|
{
|
|
if (m_f != nullptr) {
|
|
fclose(m_f);
|
|
}
|
|
delete[] m_buf;
|
|
delete[] m_tmpBuf;
|
|
}
|
|
|
|
|
|
bool PLYReader::valid() const
|
|
{
|
|
return m_valid;
|
|
}
|
|
|
|
|
|
bool PLYReader::has_element() const
|
|
{
|
|
return m_valid && m_currentElement < m_elements.size();
|
|
}
|
|
|
|
|
|
const PLYElement* PLYReader::element() const
|
|
{
|
|
assert(has_element());
|
|
return &m_elements[m_currentElement];
|
|
}
|
|
|
|
|
|
bool PLYReader::load_element()
|
|
{
|
|
assert(has_element());
|
|
if (m_elementLoaded) {
|
|
return true;
|
|
}
|
|
|
|
PLYElement& elem = m_elements[m_currentElement];
|
|
return elem.fixedSize ? load_fixed_size_element(elem) : load_variable_size_element(elem);
|
|
}
|
|
|
|
|
|
void PLYReader::next_element()
|
|
{
|
|
if (!has_element()) {
|
|
return;
|
|
}
|
|
|
|
// If the element was loaded, the read buffer should already be positioned at
|
|
// the start of the next element.
|
|
PLYElement& elem = m_elements[m_currentElement];
|
|
m_currentElement++;
|
|
|
|
if (m_elementLoaded) {
|
|
// Clear any temporary storage used for list properties in the current element.
|
|
for (PLYProperty& prop : elem.properties) {
|
|
if (prop.countType == PLYPropertyType::None) {
|
|
continue;
|
|
}
|
|
prop.listData.clear();
|
|
prop.listData.shrink_to_fit();
|
|
prop.rowCount.clear();
|
|
prop.rowCount.shrink_to_fit();
|
|
}
|
|
|
|
// Clear temporary storage for the non-list properties in the current element.
|
|
m_elementData.clear();
|
|
m_elementLoaded = false;
|
|
return;
|
|
}
|
|
|
|
// If the element wasn't loaded, we have to move the file pointer past its
|
|
// contents. How we do that depends on whether this is an ASCII or binary
|
|
// file and, if it's a binary, whether the element is fixed or variable
|
|
// size.
|
|
if (m_fileType == PLYFileType::ASCII) {
|
|
for (uint32_t row = 0; row < elem.count; row++) {
|
|
next_line();
|
|
}
|
|
}
|
|
else if (elem.fixedSize) {
|
|
int64_t elementStart = static_cast<int64_t>(m_pos - m_buf);
|
|
int64_t elementSize = elem.rowStride * elem.count;
|
|
int64_t elementEnd = elementStart + elementSize;
|
|
if (elementEnd >= kPLYReadBufferSize) {
|
|
m_bufOffset += elementEnd;
|
|
file_seek(m_f, m_bufOffset, SEEK_SET);
|
|
m_bufEnd = m_buf + kPLYReadBufferSize;
|
|
m_pos = m_bufEnd;
|
|
m_end = m_bufEnd;
|
|
refill_buffer();
|
|
}
|
|
else {
|
|
m_pos = m_buf + elementEnd;
|
|
m_end = m_pos;
|
|
}
|
|
}
|
|
else if (m_fileType == PLYFileType::Binary) {
|
|
for (uint32_t row = 0; row < elem.count; row++) {
|
|
for (const PLYProperty& prop : elem.properties) {
|
|
if (prop.countType == PLYPropertyType::None) {
|
|
uint32_t numBytes = kPLYPropertySize[uint32_t(prop.type)];
|
|
if (m_pos + numBytes > m_bufEnd) {
|
|
if (!refill_buffer() || m_pos + numBytes > m_bufEnd) {
|
|
m_valid = false;
|
|
return;
|
|
}
|
|
}
|
|
m_pos += numBytes;
|
|
m_end = m_pos;
|
|
continue;
|
|
}
|
|
|
|
uint32_t numBytes = kPLYPropertySize[uint32_t(prop.countType)];
|
|
if (m_pos + numBytes > m_bufEnd) {
|
|
if (!refill_buffer() || m_pos + numBytes > m_bufEnd) {
|
|
m_valid = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
int count = 0;
|
|
copy_and_convert_to(&count, reinterpret_cast<const uint8_t*>(m_pos), prop.countType);
|
|
|
|
if (count < 0) {
|
|
m_valid = false;
|
|
return;
|
|
}
|
|
|
|
numBytes += uint32_t(count) * kPLYPropertySize[uint32_t(prop.type)];
|
|
if (m_pos + numBytes > m_bufEnd) {
|
|
if (!refill_buffer() || m_pos + numBytes > m_bufEnd) {
|
|
m_valid = false;
|
|
return;
|
|
}
|
|
}
|
|
m_pos += numBytes;
|
|
m_end = m_pos;
|
|
}
|
|
}
|
|
}
|
|
else { // PLYFileType::BinaryBigEndian
|
|
for (uint32_t row = 0; row < elem.count; row++) {
|
|
for (const PLYProperty& prop : elem.properties) {
|
|
if (prop.countType == PLYPropertyType::None) {
|
|
uint32_t numBytes = kPLYPropertySize[uint32_t(prop.type)];
|
|
if (m_pos + numBytes > m_bufEnd) {
|
|
if (!refill_buffer() || m_pos + numBytes > m_bufEnd) {
|
|
m_valid = false;
|
|
return;
|
|
}
|
|
}
|
|
m_pos += numBytes;
|
|
m_end = m_pos;
|
|
continue;
|
|
}
|
|
|
|
uint32_t numBytes = kPLYPropertySize[uint32_t(prop.countType)];
|
|
if (m_pos + numBytes > m_bufEnd) {
|
|
if (!refill_buffer() || m_pos + numBytes > m_bufEnd) {
|
|
m_valid = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
int count = 0;
|
|
uint8_t tmp[8];
|
|
memcpy(tmp, m_pos, numBytes);
|
|
endian_swap(tmp, prop.countType);
|
|
copy_and_convert_to(&count, tmp, prop.countType);
|
|
|
|
if (count < 0) {
|
|
m_valid = false;
|
|
return;
|
|
}
|
|
|
|
numBytes += uint32_t(count) * kPLYPropertySize[uint32_t(prop.type)];
|
|
if (m_pos + numBytes > m_bufEnd) {
|
|
if (!refill_buffer() || m_pos + numBytes > m_bufEnd) {
|
|
m_valid = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_pos += numBytes;
|
|
m_end = m_pos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
PLYFileType PLYReader::file_type() const
|
|
{
|
|
return m_fileType;
|
|
}
|
|
|
|
|
|
int PLYReader::version_major() const
|
|
{
|
|
return m_majorVersion;
|
|
}
|
|
|
|
|
|
int PLYReader::version_minor() const
|
|
{
|
|
return m_minorVersion;
|
|
}
|
|
|
|
|
|
uint32_t PLYReader::num_elements() const
|
|
{
|
|
return m_valid ? static_cast<uint32_t>(m_elements.size()) : 0;
|
|
}
|
|
|
|
|
|
uint32_t PLYReader::find_element(const char* name) const
|
|
{
|
|
for (uint32_t i = 0, endI = num_elements(); i < endI; i++) {
|
|
const PLYElement& elem = m_elements[i];
|
|
if (strcmp(elem.name.c_str(), name) == 0) {
|
|
return i;
|
|
}
|
|
}
|
|
return kInvalidIndex;
|
|
}
|
|
|
|
|
|
PLYElement* PLYReader::get_element(uint32_t idx)
|
|
{
|
|
return (idx < num_elements()) ? &m_elements[idx] : nullptr;
|
|
}
|
|
|
|
|
|
bool PLYReader::element_is(const char* name) const
|
|
{
|
|
return has_element() && strcmp(element()->name.c_str(), name) == 0;
|
|
}
|
|
|
|
|
|
uint32_t PLYReader::num_rows() const
|
|
{
|
|
return has_element() ? element()->count : 0;
|
|
}
|
|
|
|
|
|
uint32_t PLYReader::find_property(const char* name) const
|
|
{
|
|
return has_element() ? element()->find_property(name) : kInvalidIndex;
|
|
}
|
|
|
|
|
|
bool PLYReader::find_properties(uint32_t propIdxs[], uint32_t numIdxs, ...) const
|
|
{
|
|
if (!has_element()) {
|
|
return false;
|
|
}
|
|
va_list args;
|
|
va_start(args, numIdxs);
|
|
bool foundAll = element()->find_properties_va(propIdxs, numIdxs, args);
|
|
va_end(args);
|
|
return foundAll;
|
|
}
|
|
|
|
|
|
bool PLYReader::extract_properties(const uint32_t propIdxs[], uint32_t numProps, PLYPropertyType destType, void *dest) const
|
|
{
|
|
if (numProps == 0) {
|
|
return false;
|
|
}
|
|
|
|
const PLYElement* elem = element();
|
|
|
|
// Make sure all property indexes are valid and that none of the properties
|
|
// are lists (this function only extracts non-list data).
|
|
for (uint32_t i = 0; i < numProps; i++) {
|
|
if (propIdxs[i] >= elem->properties.size()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Find out whether we have contiguous columns. If so, we may be able to
|
|
// use a more efficient data extraction technique.
|
|
bool contiguousCols = true;
|
|
uint32_t expectedOffset = elem->properties[propIdxs[0]].offset;
|
|
for (uint32_t i = 0; i < numProps; i++) {
|
|
uint32_t propIdx = propIdxs[i];
|
|
const PLYProperty& prop = elem->properties[propIdx];
|
|
if (prop.offset != expectedOffset) {
|
|
contiguousCols = false;
|
|
break;
|
|
}
|
|
expectedOffset = prop.offset + kPLYPropertySize[uint32_t(prop.type)];
|
|
}
|
|
|
|
// If the row we're extracting is contiguous in memory (i.e. there are no
|
|
// gaps anywhere in a row - start, end or middle), we can use an even MORE
|
|
// efficient data extraction technique.
|
|
bool contiguousRows = contiguousCols &&
|
|
(elem->properties[propIdxs[0]].offset == 0) &&
|
|
(expectedOffset == elem->rowStride);
|
|
|
|
// If no data conversion is required, we can memcpy chunks of data
|
|
// directly over to `dest`. How big those chunks will be depends on whether
|
|
// the columns and/or rows are contiguous, as determined above.
|
|
bool conversionRequired = false;
|
|
for (uint32_t i = 0; i < numProps; i++) {
|
|
uint32_t propIdx = propIdxs[i];
|
|
const PLYProperty& prop = elem->properties[propIdx];
|
|
if (!compatible_types(prop.type, destType)) {
|
|
conversionRequired = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint8_t* to = reinterpret_cast<uint8_t*>(dest);
|
|
if (!conversionRequired) {
|
|
// If no data conversion is required, we can just use memcpy to get
|
|
// values into dest.
|
|
if (contiguousRows) {
|
|
// Most efficient case is when the rows are contiguous. It means we're
|
|
// simply copying the entire data block for this element, which we can
|
|
// do with a single memcpy.
|
|
std::memcpy(to, m_elementData.data(), m_elementData.size());
|
|
}
|
|
else if (contiguousCols) {
|
|
// If the rows aren't contiguous, but the columns we're extracting
|
|
// within each row are, then we can do a single memcpy per row.
|
|
const uint8_t* from = m_elementData.data() + elem->properties[propIdxs[0]].offset;
|
|
const uint8_t* end = m_elementData.data() + m_elementData.size();
|
|
const size_t numBytes = expectedOffset - elem->properties[propIdxs[0]].offset;
|
|
while (from < end) {
|
|
std::memcpy(to, from, numBytes);
|
|
from += elem->rowStride;
|
|
to += numBytes;
|
|
}
|
|
}
|
|
else {
|
|
// If the columns aren't contiguous, we must memcpy each one separately.
|
|
const uint8_t* row = m_elementData.data();
|
|
const uint8_t* end = m_elementData.data() + m_elementData.size();
|
|
uint8_t* to = reinterpret_cast<uint8_t*>(dest);
|
|
size_t colBytes = kPLYPropertySize[uint32_t(destType)]; // size of an output column in bytes.
|
|
while (row < end) {
|
|
for (uint32_t i = 0; i < numProps; i++) {
|
|
uint32_t propIdx = propIdxs[i];
|
|
const PLYProperty& prop = elem->properties[propIdx];
|
|
std::memcpy(to, row + prop.offset, colBytes);
|
|
to += colBytes;
|
|
}
|
|
row += elem->rowStride;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// We will have to do data type conversions on the column values here. We
|
|
// cannot simply use memcpy in this case, every column has to be
|
|
// processed separately.
|
|
const uint8_t* row = m_elementData.data();
|
|
const uint8_t* end = m_elementData.data() + m_elementData.size();
|
|
uint8_t* to = reinterpret_cast<uint8_t*>(dest);
|
|
size_t colBytes = kPLYPropertySize[uint32_t(destType)]; // size of an output column in bytes.
|
|
while (row < end) {
|
|
for (uint32_t i = 0; i < numProps; i++) {
|
|
uint32_t propIdx = propIdxs[i];
|
|
const PLYProperty& prop = elem->properties[propIdx];
|
|
copy_and_convert(to, destType, row + prop.offset, prop.type);
|
|
to += colBytes;
|
|
}
|
|
row += elem->rowStride;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYReader::extract_properties_with_stride(const uint32_t propIdxs[], uint32_t numProps, PLYPropertyType destType, void *dest, uint32_t destStride) const
|
|
{
|
|
if (numProps == 0) {
|
|
return false;
|
|
}
|
|
|
|
// The destination stride must be greater than or equal to the combined
|
|
// size of all properties we're extracting. Zero is treated as a special
|
|
// value meaning packed with no spacing.
|
|
const uint32_t minDestStride = numProps * kPLYPropertySize[uint32_t(destType)];
|
|
if (destStride == 0 || destStride == minDestStride) {
|
|
return extract_properties(propIdxs, numProps, destType, dest);
|
|
}
|
|
else if (destStride < minDestStride) {
|
|
return false;
|
|
}
|
|
|
|
const PLYElement* elem = element();
|
|
|
|
// Make sure all property indexes are valid and that none of the properties
|
|
// are lists (this function only extracts non-list data).
|
|
for (uint32_t i = 0; i < numProps; i++) {
|
|
if (propIdxs[i] >= elem->properties.size()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Find out whether we have contiguous columns. If so, we may be able to
|
|
// use a more efficient data extraction technique.
|
|
bool contiguousCols = true;
|
|
uint32_t expectedOffset = elem->properties[propIdxs[0]].offset;
|
|
for (uint32_t i = 0; i < numProps; i++) {
|
|
uint32_t propIdx = propIdxs[i];
|
|
const PLYProperty& prop = elem->properties[propIdx];
|
|
if (prop.offset != expectedOffset) {
|
|
contiguousCols = false;
|
|
break;
|
|
}
|
|
expectedOffset = prop.offset + kPLYPropertySize[uint32_t(prop.type)];
|
|
}
|
|
|
|
// If no data conversion is required, we can memcpy chunks of data
|
|
// directly over to `dest`. How big those chunks will be depends on whether
|
|
// the columns and/or rows are contiguous, as determined above.
|
|
bool conversionRequired = false;
|
|
for (uint32_t i = 0; i < numProps; i++) {
|
|
uint32_t propIdx = propIdxs[i];
|
|
const PLYProperty& prop = elem->properties[propIdx];
|
|
if (!compatible_types(prop.type, destType)) {
|
|
conversionRequired = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint8_t* to = reinterpret_cast<uint8_t*>(dest);
|
|
if (!conversionRequired) {
|
|
// If no data conversion is required, we can just use memcpy to get
|
|
// values into dest. When the destination requires some padding between
|
|
// rows, the best we can do is a memcpy per row.
|
|
if (contiguousCols) {
|
|
// If the rows aren't contiguous, but the columns we're extracting
|
|
// within each row are, then we can do a single memcpy per row.
|
|
const uint8_t* from = m_elementData.data() + elem->properties[propIdxs[0]].offset;
|
|
const uint8_t* end = m_elementData.data() + m_elementData.size();
|
|
const size_t numBytes = expectedOffset - elem->properties[propIdxs[0]].offset;
|
|
while (from < end) {
|
|
std::memcpy(to, from, numBytes);
|
|
from += elem->rowStride;
|
|
to += destStride;
|
|
}
|
|
}
|
|
else {
|
|
// If the columns aren't contiguous, we must memcpy each one separately.
|
|
const uint8_t* row = m_elementData.data();
|
|
const uint8_t* end = m_elementData.data() + m_elementData.size();
|
|
uint8_t* to = reinterpret_cast<uint8_t*>(dest);
|
|
const size_t colBytes = kPLYPropertySize[uint32_t(destType)]; // size of an output column in bytes.
|
|
const size_t colPadding = destStride - minDestStride;
|
|
while (row < end) {
|
|
for (uint32_t i = 0; i < numProps; i++) {
|
|
uint32_t propIdx = propIdxs[i];
|
|
const PLYProperty& prop = elem->properties[propIdx];
|
|
std::memcpy(to, row + prop.offset, colBytes);
|
|
to += colBytes;
|
|
}
|
|
row += elem->rowStride;
|
|
to += colPadding;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// We will have to do data type conversions on the column values here. We
|
|
// cannot simply use memcpy in this case, every column has to be
|
|
// processed separately.
|
|
const uint8_t* row = m_elementData.data();
|
|
const uint8_t* end = m_elementData.data() + m_elementData.size();
|
|
uint8_t* to = reinterpret_cast<uint8_t*>(dest);
|
|
size_t colBytes = kPLYPropertySize[uint32_t(destType)]; // size of an output column in bytes.
|
|
size_t colPadding = destStride - minDestStride;
|
|
while (row < end) {
|
|
for (uint32_t i = 0; i < numProps; i++) {
|
|
uint32_t propIdx = propIdxs[i];
|
|
const PLYProperty& prop = elem->properties[propIdx];
|
|
copy_and_convert(to, destType, row + prop.offset, prop.type);
|
|
to += colBytes;
|
|
}
|
|
row += elem->rowStride;
|
|
to += colPadding;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
const uint32_t* PLYReader::get_list_counts(uint32_t propIdx) const
|
|
{
|
|
if (!has_element() || propIdx >= element()->properties.size() || element()->properties[propIdx].countType == PLYPropertyType::None) {
|
|
return nullptr;
|
|
}
|
|
return element()->properties[propIdx].rowCount.data();
|
|
}
|
|
|
|
|
|
uint32_t PLYReader::sum_of_list_counts(uint32_t propIdx) const
|
|
{
|
|
if (!has_element() || propIdx >= element()->properties.size() || element()->properties[propIdx].countType == PLYPropertyType::None) {
|
|
return 0;
|
|
}
|
|
const PLYProperty& prop = element()->properties[propIdx];
|
|
return prop.listData.size() / kPLYPropertySize[uint32_t(prop.type)];
|
|
}
|
|
|
|
|
|
const uint8_t* PLYReader::get_list_data(uint32_t propIdx) const
|
|
{
|
|
if (!has_element() || propIdx >= element()->properties.size() || element()->properties[propIdx].countType == PLYPropertyType::None) {
|
|
return nullptr;
|
|
}
|
|
return element()->properties[propIdx].listData.data();
|
|
}
|
|
|
|
|
|
bool PLYReader::extract_list_property(uint32_t propIdx, PLYPropertyType destType, void *dest) const
|
|
{
|
|
if (!has_element() || propIdx >= element()->properties.size() || element()->properties[propIdx].countType == PLYPropertyType::None) {
|
|
return false;
|
|
}
|
|
|
|
const PLYProperty& prop = element()->properties[propIdx];
|
|
if (compatible_types(prop.type, destType)) {
|
|
// If no type conversion is required, we can just copy the list data
|
|
// directly over with a single memcpy.
|
|
std::memcpy(dest, prop.listData.data(), prop.listData.size());
|
|
}
|
|
else {
|
|
// If type conversion is required we'll have to process each list value separately.
|
|
const uint8_t* from = prop.listData.data();
|
|
const uint8_t* end = prop.listData.data() + prop.listData.size();
|
|
uint8_t* to = reinterpret_cast<uint8_t*>(dest);
|
|
const size_t toBytes = kPLYPropertySize[uint32_t(destType)];
|
|
const size_t fromBytes = kPLYPropertySize[uint32_t(prop.type)];
|
|
while (from < end) {
|
|
copy_and_convert(to, destType, from, prop.type);
|
|
to += toBytes;
|
|
from += fromBytes;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
uint32_t PLYReader::num_triangles(uint32_t propIdx) const
|
|
{
|
|
const uint32_t* counts = get_list_counts(propIdx);
|
|
if (counts == nullptr) {
|
|
return 0;
|
|
}
|
|
|
|
const uint32_t numRows = element()->count;
|
|
uint32_t num = 0;
|
|
for (uint32_t i = 0; i < numRows; i++) {
|
|
if (counts[i] >= 3) {
|
|
num += counts[i] - 2;
|
|
}
|
|
}
|
|
return num;
|
|
}
|
|
|
|
|
|
bool PLYReader::requires_triangulation(uint32_t propIdx) const
|
|
{
|
|
const uint32_t* counts = get_list_counts(propIdx);
|
|
if (counts == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
const uint32_t numRows = element()->count;
|
|
for (uint32_t i = 0; i < numRows; i++) {
|
|
if (counts[i] != 3) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool PLYReader::extract_triangles(uint32_t propIdx, const float pos[], uint32_t numVerts, PLYPropertyType destType, void *dest) const
|
|
{
|
|
if (!requires_triangulation(propIdx)) {
|
|
return extract_list_property(propIdx, destType, dest);
|
|
}
|
|
|
|
const PLYElement* elem = element();
|
|
const PLYProperty& prop = elem->properties[propIdx];
|
|
|
|
const uint32_t* counts = prop.rowCount.data();
|
|
const uint8_t* data = prop.listData.data();
|
|
|
|
uint8_t* to = reinterpret_cast<uint8_t*>(dest);
|
|
|
|
bool convertSrc = !compatible_types(elem->properties[propIdx].type, PLYPropertyType::Int);
|
|
bool convertDst = !compatible_types(PLYPropertyType::Int, destType);
|
|
|
|
size_t srcValBytes = kPLYPropertySize[uint32_t(prop.type)];
|
|
size_t destValBytes = kPLYPropertySize[uint32_t(destType)];
|
|
|
|
if (convertSrc && convertDst) {
|
|
std::vector<int> faceIndices, triIndices;
|
|
faceIndices.reserve(32);
|
|
triIndices.reserve(64);
|
|
const uint8_t* face = data;
|
|
for (uint32_t faceIdx = 0; faceIdx < elem->count; faceIdx++) {
|
|
const uint8_t* faceEnd = face + srcValBytes * counts[faceIdx];
|
|
faceIndices.clear();
|
|
faceIndices.reserve(counts[faceIdx]);
|
|
for (; face < faceEnd; face += srcValBytes) {
|
|
int idx = -1;
|
|
copy_and_convert_to(&idx, face, prop.type);
|
|
faceIndices.push_back(idx);
|
|
}
|
|
|
|
triIndices.resize((counts[faceIdx] - 2) * 3);
|
|
triangulate_polygon(counts[faceIdx], pos, numVerts, faceIndices.data(), triIndices.data());
|
|
for (int idx : triIndices) {
|
|
copy_and_convert(to, destType, reinterpret_cast<const uint8_t*>(&idx), PLYPropertyType::Int);
|
|
to += destValBytes;
|
|
}
|
|
}
|
|
}
|
|
else if (convertSrc) {
|
|
std::vector<int> faceIndices;
|
|
faceIndices.reserve(32);
|
|
const uint8_t* face = data;
|
|
for (uint32_t faceIdx = 0; faceIdx < elem->count; faceIdx++) {
|
|
const uint8_t* faceEnd = face + srcValBytes * counts[faceIdx];
|
|
faceIndices.clear();
|
|
faceIndices.reserve(counts[faceIdx]);
|
|
for (; face < faceEnd; face += srcValBytes) {
|
|
int idx = -1;
|
|
copy_and_convert_to(&idx, face, prop.type);
|
|
faceIndices.push_back(idx);
|
|
}
|
|
|
|
uint32_t numTris = triangulate_polygon(counts[faceIdx], pos, numVerts, faceIndices.data(), reinterpret_cast<int*>(to));
|
|
to += numTris * 3 * destValBytes;
|
|
}
|
|
}
|
|
else if (convertDst) {
|
|
std::vector<int> triIndices;
|
|
triIndices.reserve(64);
|
|
const uint8_t* face = data;
|
|
for (uint32_t faceIdx = 0; faceIdx < elem->count; faceIdx++) {
|
|
triIndices.resize((counts[faceIdx] - 2) * 3);
|
|
triangulate_polygon(counts[faceIdx], pos, numVerts, reinterpret_cast<const int*>(face), triIndices.data());
|
|
for (int idx : triIndices) {
|
|
copy_and_convert(to, destType, reinterpret_cast<const uint8_t*>(&idx), PLYPropertyType::Int);
|
|
to += destValBytes;
|
|
}
|
|
face += srcValBytes * counts[faceIdx];
|
|
}
|
|
}
|
|
else {
|
|
const uint8_t* face = data;
|
|
for (uint32_t faceIdx = 0; faceIdx < elem->count; faceIdx++) {
|
|
uint32_t numTris = triangulate_polygon(counts[faceIdx], pos, numVerts, reinterpret_cast<const int*>(face), reinterpret_cast<int*>(to));
|
|
face += counts[faceIdx] * srcValBytes;
|
|
to += numTris * 3 * destValBytes;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYReader::find_pos(uint32_t propIdxs[3]) const
|
|
{
|
|
return find_properties(propIdxs, 3, "x", "y", "z");
|
|
}
|
|
|
|
|
|
bool PLYReader::find_normal(uint32_t propIdxs[3]) const
|
|
{
|
|
return find_properties(propIdxs, 3, "nx", "ny", "nz");
|
|
}
|
|
|
|
|
|
bool PLYReader::find_texcoord(uint32_t propIdxs[2]) const
|
|
{
|
|
return find_properties(propIdxs, 2, "u", "v") ||
|
|
find_properties(propIdxs, 2, "s", "t") ||
|
|
find_properties(propIdxs, 2, "texture_u", "texture_v") ||
|
|
find_properties(propIdxs, 2, "texture_s", "texture_t");
|
|
}
|
|
|
|
|
|
bool PLYReader::find_color(uint32_t propIdxs[3]) const
|
|
{
|
|
return find_properties(propIdxs, 3, "r", "g", "b") ||
|
|
find_properties(propIdxs, 3, "red", "green", "blue");
|
|
}
|
|
|
|
|
|
bool PLYReader::find_indices(uint32_t propIdxs[1]) const
|
|
{
|
|
return find_properties(propIdxs, 1, "vertex_indices") ||
|
|
find_properties(propIdxs, 1, "vertex_index");
|
|
}
|
|
|
|
|
|
//
|
|
// PLYReader private methods
|
|
//
|
|
|
|
bool PLYReader::refill_buffer()
|
|
{
|
|
if (m_f == nullptr || m_atEOF) {
|
|
// Nothing left to read.
|
|
return false;
|
|
}
|
|
|
|
if (m_pos == m_buf && m_end == m_bufEnd) {
|
|
// Can't make any more room in the buffer!
|
|
return false;
|
|
}
|
|
|
|
// Move everything from the start of the current token onwards, to the
|
|
// start of the read buffer.
|
|
int64_t bufSize = static_cast<int64_t>(m_bufEnd - m_buf);
|
|
if (bufSize < kPLYReadBufferSize) {
|
|
m_buf[bufSize] = m_buf[kPLYReadBufferSize];
|
|
m_buf[kPLYReadBufferSize] = '\0';
|
|
m_bufEnd = m_buf + kPLYReadBufferSize;
|
|
}
|
|
size_t keep = static_cast<size_t>(m_bufEnd - m_pos);
|
|
if (keep > 0 && m_pos > m_buf) {
|
|
std::memmove(m_buf, m_pos, sizeof(char) * keep);
|
|
m_bufOffset += static_cast<int64_t>(m_pos - m_buf);
|
|
}
|
|
m_end = m_buf + (m_end - m_pos);
|
|
m_pos = m_buf;
|
|
|
|
// Fill the remaining space in the buffer with data from the file.
|
|
size_t fetched = fread(m_buf + keep, sizeof(char), kPLYReadBufferSize - keep, m_f) + keep;
|
|
m_atEOF = fetched < kPLYReadBufferSize;
|
|
m_bufEnd = m_buf + fetched;
|
|
|
|
if (!m_inDataSection || m_fileType == PLYFileType::ASCII) {
|
|
return rewind_to_safe_char();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYReader::rewind_to_safe_char()
|
|
{
|
|
// If it looks like a token might run past the end of this buffer, move
|
|
// the buffer end pointer back before it & rewind the file. This way the
|
|
// next refill will pick up the whole of the token.
|
|
if (!m_atEOF && (m_bufEnd[-1] == '\n' || !is_safe_buffer_end(m_bufEnd[-1]))) {
|
|
const char* safe = m_bufEnd - 2;
|
|
// If '\n' is the last char in the buffer, then a call to `next_line()`
|
|
// will move `m_pos` to point at the null terminator but won't refresh
|
|
// the buffer. It would be clearer to fix this in `next_line()` but I
|
|
// believe it'll be more performant to simply treat `\n` as an unsafe
|
|
// character here.
|
|
while (safe >= m_end && (*safe == '\n' || !is_safe_buffer_end(*safe))) {
|
|
--safe;
|
|
}
|
|
if (safe < m_end) {
|
|
// No safe places to rewind to in the whole buffer!
|
|
return false;
|
|
}
|
|
++safe;
|
|
m_buf[kPLYReadBufferSize] = *safe;
|
|
m_bufEnd = safe;
|
|
}
|
|
m_buf[m_bufEnd - m_buf] = '\0';
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYReader::accept()
|
|
{
|
|
m_pos = m_end;
|
|
return true;
|
|
}
|
|
|
|
|
|
// Advances to end of line or to next non-whitespace char.
|
|
bool PLYReader::advance()
|
|
{
|
|
m_pos = m_end;
|
|
while (true) {
|
|
while (is_whitespace(*m_pos)) {
|
|
++m_pos;
|
|
}
|
|
if (m_pos == m_bufEnd) {
|
|
m_end = m_pos;
|
|
if (refill_buffer()) {
|
|
continue;
|
|
}
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
m_end = m_pos;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYReader::next_line()
|
|
{
|
|
m_pos = m_end;
|
|
do {
|
|
while (*m_pos != '\n') {
|
|
if (m_pos == m_bufEnd) {
|
|
m_end = m_pos;
|
|
if (refill_buffer()) {
|
|
continue;
|
|
}
|
|
return false;
|
|
}
|
|
++m_pos;
|
|
}
|
|
++m_pos; // move past the newline char
|
|
m_end = m_pos;
|
|
} while (match("comment") || match("obj_info"));
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYReader::match(const char* str)
|
|
{
|
|
m_end = m_pos;
|
|
while (m_end < m_bufEnd && *str != '\0' && *m_end == *str) {
|
|
++m_end;
|
|
++str;
|
|
}
|
|
if (*str != '\0') {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYReader::which(const char* values[], uint32_t* index)
|
|
{
|
|
for (uint32_t i = 0; values[i] != nullptr; i++) {
|
|
if (keyword(values[i])) {
|
|
*index = i;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool PLYReader::which_property_type(PLYPropertyType* type)
|
|
{
|
|
for (uint32_t i = 0; kTypeAliases[i].name != nullptr; i++) {
|
|
if (keyword(kTypeAliases[i].name)) {
|
|
*type = kTypeAliases[i].type;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool PLYReader::keyword(const char* kw)
|
|
{
|
|
return match(kw) && !is_keyword_part(*m_end);
|
|
}
|
|
|
|
|
|
bool PLYReader::identifier(char* dest, size_t destLen)
|
|
{
|
|
m_end = m_pos;
|
|
if (!is_keyword_start(*m_end) || destLen == 0) {
|
|
return false;
|
|
}
|
|
do {
|
|
++m_end;
|
|
} while (is_keyword_part(*m_end));
|
|
|
|
size_t len = static_cast<size_t>(m_end - m_pos);
|
|
if (len >= destLen) {
|
|
return false; // identifier too large for dest!
|
|
}
|
|
std::memcpy(dest, m_pos, sizeof(char) * len);
|
|
dest[len] = '\0';
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYReader::int_literal(int* value)
|
|
{
|
|
return miniply::int_literal(m_pos, &m_end, value);
|
|
}
|
|
|
|
|
|
bool PLYReader::float_literal(float* value)
|
|
{
|
|
return miniply::float_literal(m_pos, &m_end, value);
|
|
}
|
|
|
|
|
|
bool PLYReader::double_literal(double* value)
|
|
{
|
|
return miniply::double_literal(m_pos, &m_end, value);
|
|
}
|
|
|
|
|
|
bool PLYReader::parse_elements()
|
|
{
|
|
m_elements.reserve(4);
|
|
while (m_valid && keyword("element")) {
|
|
parse_element();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYReader::parse_element()
|
|
{
|
|
int count = 0;
|
|
|
|
m_valid = keyword("element") && advance() &&
|
|
identifier(m_tmpBuf, kPLYTempBufferSize) && advance() &&
|
|
int_literal(&count) && next_line();
|
|
if (!m_valid || count < 0) {
|
|
return false;
|
|
}
|
|
|
|
m_elements.push_back(PLYElement());
|
|
PLYElement& elem = m_elements.back();
|
|
elem.name = m_tmpBuf;
|
|
elem.count = static_cast<uint32_t>(count);
|
|
elem.properties.reserve(10);
|
|
|
|
while (m_valid && keyword("property")) {
|
|
parse_property(elem.properties);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYReader::parse_property(std::vector<PLYProperty>& properties)
|
|
{
|
|
PLYPropertyType type = PLYPropertyType::None;
|
|
PLYPropertyType countType = PLYPropertyType::None;
|
|
|
|
m_valid = keyword("property") && advance();
|
|
if (!m_valid) {
|
|
return false;
|
|
}
|
|
|
|
if (keyword("list")) {
|
|
// This is a list property
|
|
m_valid = advance() && which_property_type(&countType) && advance();
|
|
if (!m_valid) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
m_valid = which_property_type(&type) && advance() &&
|
|
identifier(m_tmpBuf, kPLYTempBufferSize) && next_line();
|
|
if (!m_valid) {
|
|
return false;
|
|
}
|
|
|
|
properties.push_back(PLYProperty());
|
|
PLYProperty& prop = properties.back();
|
|
prop.name = m_tmpBuf;
|
|
prop.type = type;
|
|
prop.countType = countType;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYReader::load_fixed_size_element(PLYElement& elem)
|
|
{
|
|
size_t numBytes = static_cast<size_t>(elem.count) * elem.rowStride;
|
|
|
|
m_elementData.resize(numBytes);
|
|
|
|
if (m_fileType == PLYFileType::ASCII) {
|
|
size_t back = 0;
|
|
|
|
for (uint32_t row = 0; row < elem.count; row++) {
|
|
for (PLYProperty& prop : elem.properties) {
|
|
if (!load_ascii_scalar_property(prop, back)) {
|
|
m_valid = false;
|
|
return false;
|
|
}
|
|
}
|
|
next_line();
|
|
}
|
|
}
|
|
else {
|
|
uint8_t* dst = m_elementData.data();
|
|
uint8_t* dstEnd = dst + numBytes;
|
|
while (dst < dstEnd) {
|
|
size_t bytesAvailable = static_cast<size_t>(m_bufEnd - m_pos);
|
|
if (dst + bytesAvailable > dstEnd) {
|
|
bytesAvailable = static_cast<size_t>(dstEnd - dst);
|
|
}
|
|
std::memcpy(dst, m_pos, bytesAvailable);
|
|
m_pos += bytesAvailable;
|
|
m_end = m_pos;
|
|
dst += bytesAvailable;
|
|
if (!refill_buffer()) {
|
|
break;
|
|
}
|
|
}
|
|
if (dst < dstEnd) {
|
|
m_valid = false;
|
|
return false;
|
|
}
|
|
|
|
// We assume the CPU is little endian, so if the file is big-endian we
|
|
// need to do an endianness swap on every data item in the block.
|
|
if (m_fileType == PLYFileType::BinaryBigEndian) {
|
|
uint8_t* data = m_elementData.data();
|
|
for (uint32_t row = 0; row < elem.count; row++) {
|
|
for (PLYProperty& prop : elem.properties) {
|
|
size_t numBytes = kPLYPropertySize[uint32_t(prop.type)];
|
|
switch (numBytes) {
|
|
case 2:
|
|
endian_swap_2(data);
|
|
break;
|
|
case 4:
|
|
endian_swap_4(data);
|
|
break;
|
|
case 8:
|
|
endian_swap_8(data);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
data += numBytes;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_elementLoaded = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYReader::load_variable_size_element(PLYElement& elem)
|
|
{
|
|
m_elementData.resize(static_cast<size_t>(elem.count) * elem.rowStride);
|
|
|
|
// Preallocate enough space for each row in the property to contain three
|
|
// items. This is based on the assumptions that (a) the most common use for
|
|
// list properties is vertex indices; and (b) most faces are triangles.
|
|
// This gives a performance boost because we won't have to grow the
|
|
// listData vector as many times during loading.
|
|
for (PLYProperty& prop : elem.properties) {
|
|
if (prop.countType != PLYPropertyType::None) {
|
|
prop.listData.reserve(elem.count * kPLYPropertySize[uint32_t(prop.type)] * 3);
|
|
}
|
|
}
|
|
|
|
if (m_fileType == PLYFileType::Binary) {
|
|
size_t back = 0;
|
|
for (uint32_t row = 0; row < elem.count; row++) {
|
|
for (PLYProperty& prop : elem.properties) {
|
|
if (prop.countType == PLYPropertyType::None) {
|
|
m_valid = load_binary_scalar_property(prop, back);
|
|
}
|
|
else {
|
|
load_binary_list_property(prop);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (m_fileType == PLYFileType::ASCII) {
|
|
size_t back = 0;
|
|
for (uint32_t row = 0; row < elem.count; row++) {
|
|
for (PLYProperty& prop : elem.properties) {
|
|
if (prop.countType == PLYPropertyType::None) {
|
|
m_valid = load_ascii_scalar_property(prop, back);
|
|
}
|
|
else {
|
|
load_ascii_list_property(prop);
|
|
}
|
|
}
|
|
next_line();
|
|
}
|
|
}
|
|
else { // m_fileType == PLYFileType::BinaryBigEndian
|
|
size_t back = 0;
|
|
for (uint32_t row = 0; row < elem.count; row++) {
|
|
for (PLYProperty& prop : elem.properties) {
|
|
if (prop.countType == PLYPropertyType::None) {
|
|
m_valid = load_binary_scalar_property_big_endian(prop, back);
|
|
}
|
|
else {
|
|
load_binary_list_property_big_endian(prop);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_elementLoaded = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYReader::load_ascii_scalar_property(PLYProperty& prop, size_t& destIndex)
|
|
{
|
|
uint8_t value[8];
|
|
if (!ascii_value(prop.type, value)) {
|
|
return false;
|
|
}
|
|
|
|
size_t numBytes = kPLYPropertySize[uint32_t(prop.type)];
|
|
std::memcpy(m_elementData.data() + destIndex, value, numBytes);
|
|
destIndex += numBytes;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYReader::load_ascii_list_property(PLYProperty& prop)
|
|
{
|
|
int count = 0;
|
|
m_valid = (prop.countType < PLYPropertyType::Float) && int_literal(&count) && advance() && (count >= 0);
|
|
if (!m_valid) {
|
|
return false;
|
|
}
|
|
|
|
const size_t numBytes = kPLYPropertySize[uint32_t(prop.type)];
|
|
|
|
size_t back = prop.listData.size();
|
|
prop.rowCount.push_back(static_cast<uint32_t>(count));
|
|
prop.listData.resize(back + numBytes * size_t(count));
|
|
|
|
for (uint32_t i = 0; i < uint32_t(count); i++) {
|
|
if (!ascii_value(prop.type, prop.listData.data() + back)) {
|
|
m_valid = false;
|
|
return false;
|
|
}
|
|
back += numBytes;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYReader::load_binary_scalar_property(PLYProperty& prop, size_t& destIndex)
|
|
{
|
|
size_t numBytes = kPLYPropertySize[uint32_t(prop.type)];
|
|
if (m_pos + numBytes > m_bufEnd) {
|
|
if (!refill_buffer() || m_pos + numBytes > m_bufEnd) {
|
|
m_valid = false;
|
|
return false;
|
|
}
|
|
}
|
|
std::memcpy(m_elementData.data() + destIndex, m_pos, numBytes);
|
|
m_pos += numBytes;
|
|
m_end = m_pos;
|
|
destIndex += numBytes;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYReader::load_binary_list_property(PLYProperty& prop)
|
|
{
|
|
size_t countBytes = kPLYPropertySize[uint32_t(prop.countType)];
|
|
if (m_pos + countBytes > m_bufEnd) {
|
|
if (!refill_buffer() || m_pos + countBytes > m_bufEnd) {
|
|
m_valid = false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int count = 0;
|
|
copy_and_convert_to(&count, reinterpret_cast<const uint8_t*>(m_pos), prop.countType);
|
|
|
|
if (count < 0) {
|
|
m_valid = false;
|
|
return false;
|
|
}
|
|
|
|
m_pos += countBytes;
|
|
m_end = m_pos;
|
|
|
|
const size_t listBytes = kPLYPropertySize[uint32_t(prop.type)] * uint32_t(count);
|
|
if (m_pos + listBytes > m_bufEnd) {
|
|
if (!refill_buffer() || m_pos + listBytes > m_bufEnd) {
|
|
m_valid = false;
|
|
return false;
|
|
}
|
|
}
|
|
size_t back = prop.listData.size();
|
|
prop.rowCount.push_back(static_cast<uint32_t>(count));
|
|
prop.listData.resize(back + listBytes);
|
|
std::memcpy(prop.listData.data() + back, m_pos, listBytes);
|
|
|
|
m_pos += listBytes;
|
|
m_end = m_pos;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYReader::load_binary_scalar_property_big_endian(PLYProperty &prop, size_t &destIndex)
|
|
{
|
|
size_t startIndex = destIndex;
|
|
if (load_binary_scalar_property(prop, destIndex)) {
|
|
endian_swap(m_elementData.data() + startIndex, prop.type);
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
bool PLYReader::load_binary_list_property_big_endian(PLYProperty &prop)
|
|
{
|
|
size_t countBytes = kPLYPropertySize[uint32_t(prop.countType)];
|
|
if (m_pos + countBytes > m_bufEnd) {
|
|
if (!refill_buffer() || m_pos + countBytes > m_bufEnd) {
|
|
m_valid = false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int count = 0;
|
|
uint8_t tmp[8];
|
|
std::memcpy(tmp, m_pos, countBytes);
|
|
endian_swap(tmp, prop.countType);
|
|
copy_and_convert_to(&count, tmp, prop.countType);
|
|
|
|
if (count < 0) {
|
|
m_valid = false;
|
|
return false;
|
|
}
|
|
|
|
m_pos += countBytes;
|
|
m_end = m_pos;
|
|
|
|
const size_t typeBytes = kPLYPropertySize[uint32_t(prop.type)];
|
|
const size_t listBytes = typeBytes * uint32_t(count);
|
|
if (m_pos + listBytes > m_bufEnd) {
|
|
if (!refill_buffer() || m_pos + listBytes > m_bufEnd) {
|
|
m_valid = false;
|
|
return false;
|
|
}
|
|
}
|
|
size_t back = prop.listData.size();
|
|
prop.rowCount.push_back(static_cast<uint32_t>(count));
|
|
prop.listData.resize(back + listBytes);
|
|
|
|
uint8_t* list = prop.listData.data() + back;
|
|
std::memcpy(list, m_pos, listBytes);
|
|
endian_swap_array(list, prop.type, count);
|
|
|
|
m_pos += listBytes;
|
|
m_end = m_pos;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PLYReader::ascii_value(PLYPropertyType propType, uint8_t value[8])
|
|
{
|
|
int tmpInt = 0;
|
|
|
|
switch (propType) {
|
|
case PLYPropertyType::Char:
|
|
case PLYPropertyType::UChar:
|
|
case PLYPropertyType::Short:
|
|
case PLYPropertyType::UShort:
|
|
m_valid = int_literal(&tmpInt);
|
|
break;
|
|
case PLYPropertyType::Int:
|
|
case PLYPropertyType::UInt:
|
|
m_valid = int_literal(reinterpret_cast<int*>(value));
|
|
break;
|
|
case PLYPropertyType::Float:
|
|
m_valid = float_literal(reinterpret_cast<float*>(value));
|
|
break;
|
|
case PLYPropertyType::Double:
|
|
default:
|
|
m_valid = double_literal(reinterpret_cast<double*>(value));
|
|
break;
|
|
}
|
|
|
|
if (!m_valid) {
|
|
return false;
|
|
}
|
|
advance();
|
|
|
|
switch (propType) {
|
|
case PLYPropertyType::Char:
|
|
reinterpret_cast<int8_t*>(value)[0] = static_cast<int8_t>(tmpInt);
|
|
break;
|
|
case PLYPropertyType::UChar:
|
|
value[0] = static_cast<uint8_t>(tmpInt);
|
|
break;
|
|
case PLYPropertyType::Short:
|
|
reinterpret_cast<int16_t*>(value)[0] = static_cast<int16_t>(tmpInt);
|
|
break;
|
|
case PLYPropertyType::UShort:
|
|
reinterpret_cast<uint16_t*>(value)[0] = static_cast<uint16_t>(tmpInt);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// Polygon triangulation
|
|
//
|
|
|
|
static float angle_at_vert(uint32_t idx,
|
|
const std::vector<Vec2>& points2D,
|
|
const std::vector<uint32_t>& prev,
|
|
const std::vector<uint32_t>& next)
|
|
{
|
|
Vec2 xaxis = normalize(points2D[next[idx]] - points2D[idx]);
|
|
Vec2 yaxis = Vec2{-xaxis.y, xaxis.x};
|
|
Vec2 p2p0 = points2D[prev[idx]] - points2D[idx];
|
|
float angle = std::atan2(dot(p2p0, yaxis), dot(p2p0, xaxis));
|
|
if (angle <= 0.0f || angle >= kPi) {
|
|
angle = 10000.0f;
|
|
}
|
|
return angle;
|
|
}
|
|
|
|
|
|
uint32_t triangulate_polygon(uint32_t n, const float pos[], uint32_t numVerts, const int indices[], int dst[])
|
|
{
|
|
if (n < 3) {
|
|
return 0;
|
|
}
|
|
else if (n == 3) {
|
|
dst[0] = indices[0];
|
|
dst[1] = indices[1];
|
|
dst[2] = indices[2];
|
|
return 1;
|
|
}
|
|
else if (n == 4) {
|
|
dst[0] = indices[0];
|
|
dst[1] = indices[1];
|
|
dst[2] = indices[3];
|
|
|
|
dst[3] = indices[2];
|
|
dst[4] = indices[3];
|
|
dst[5] = indices[1];
|
|
return 2;
|
|
}
|
|
|
|
// Check that all indices for this face are in the valid range before we
|
|
// try to dereference them.
|
|
for (uint32_t i = 0; i < n; i++) {
|
|
if (indices[i] < 0 || uint32_t(indices[i]) >= numVerts) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
const Vec3* vpos = reinterpret_cast<const Vec3*>(pos);
|
|
|
|
// Calculate the geometric normal of the face
|
|
Vec3 origin = vpos[indices[0]];
|
|
Vec3 faceU = normalize(vpos[indices[1]] - origin);
|
|
Vec3 faceNormal = normalize(cross(faceU, normalize(vpos[indices[n - 1]] - origin)));
|
|
Vec3 faceV = normalize(cross(faceNormal, faceU));
|
|
|
|
// Project the faces points onto the plane perpendicular to the normal.
|
|
std::vector<Vec2> points2D(n, Vec2{0.0f, 0.0f});
|
|
for (uint32_t i = 1; i < n; i++) {
|
|
Vec3 p = vpos[indices[i]] - origin;
|
|
points2D[i] = Vec2{dot(p, faceU), dot(p, faceV)};
|
|
}
|
|
|
|
std::vector<uint32_t> next(n, 0u);
|
|
std::vector<uint32_t> prev(n, 0u);
|
|
uint32_t first = 0;
|
|
for (uint32_t i = 0, j = n - 1; i < n; i++) {
|
|
next[j] = i;
|
|
prev[i] = j;
|
|
j = i;
|
|
}
|
|
|
|
// Do ear clipping.
|
|
while (n > 3) {
|
|
// Find the (remaining) vertex with the sharpest angle.
|
|
uint32_t bestI = first;
|
|
float bestAngle = angle_at_vert(first, points2D, prev, next);
|
|
for (uint32_t i = next[first]; i != first; i = next[i]) {
|
|
float angle = angle_at_vert(i, points2D, prev, next);
|
|
if (angle < bestAngle) {
|
|
bestI = i;
|
|
bestAngle = angle;
|
|
}
|
|
}
|
|
|
|
// Clip the triangle at bestI.
|
|
uint32_t nextI = next[bestI];
|
|
uint32_t prevI = prev[bestI];
|
|
|
|
dst[0] = indices[bestI];
|
|
dst[1] = indices[nextI];
|
|
dst[2] = indices[prevI];
|
|
dst += 3;
|
|
|
|
if (bestI == first) {
|
|
first = nextI;
|
|
}
|
|
next[prevI] = nextI;
|
|
prev[nextI] = prevI;
|
|
--n;
|
|
}
|
|
|
|
// Add the final triangle.
|
|
dst[0] = indices[first];
|
|
dst[1] = indices[next[first]];
|
|
dst[2] = indices[prev[first]];
|
|
|
|
return n - 2;
|
|
}
|
|
|
|
} // namespace miniply
|