Format everything.

This commit is contained in:
K. S. Ernest (iFire) Lee 2021-11-28 11:45:31 -08:00
parent b89d4cd0e0
commit 1bcdf9271e
16 changed files with 129 additions and 116 deletions

View File

@ -229,11 +229,11 @@ static void ReadMesh(
targetShapes.push_back(&shape); targetShapes.push_back(&shape);
auto& blendChannel = blendShapes.GetBlendChannel(channelIx); auto& blendChannel = blendShapes.GetBlendChannel(channelIx);
rawSurface.blendChannels.push_back( rawSurface.blendChannels.push_back(RawBlendChannel{
RawBlendChannel{static_cast<float>(blendChannel.deformPercent), static_cast<float>(blendChannel.deformPercent),
shape.normals.LayerPresent(), shape.normals.LayerPresent(),
shape.tangents.LayerPresent(), shape.tangents.LayerPresent(),
blendChannel.name}); blendChannel.name});
} }
} }

View File

@ -59,14 +59,13 @@ FbxSkinningAccess::FbxSkinningAccess(const FbxMesh* pMesh, FbxScene* pScene, Fbx
continue; continue;
} }
vertexSkinning[clusterIndices[i]].push_back(FbxVertexSkinningInfo{(int) clusterIndex, (float)clusterWeights[i]}); vertexSkinning[clusterIndices[i]].push_back(
FbxVertexSkinningInfo{(int)clusterIndex, (float)clusterWeights[i]});
} }
} }
for (int i = 0; i < vertexSkinning.size(); i++)
maxBoneInfluences = std::max((int) vertexSkinning[i].size(), maxBoneInfluences);
for (int i = 0; i < vertexSkinning.size(); i++)
maxBoneInfluences = std::max((int)vertexSkinning[i].size(), maxBoneInfluences);
} }
} }

View File

@ -23,17 +23,14 @@ struct FbxVertexSkinningInfo {
float weight; float weight;
}; };
class FbxSkinningAccess { class FbxSkinningAccess {
public: public:
FbxSkinningAccess(const FbxMesh* pMesh, FbxScene* pScene, FbxNode* pNode); FbxSkinningAccess(const FbxMesh* pMesh, FbxScene* pScene, FbxNode* pNode);
bool IsSkinned() const { bool IsSkinned() const {
return (vertexSkinning.size() > 0); return (vertexSkinning.size() > 0);
} }
int GetNodeCount() const { int GetNodeCount() const {
return (int)jointNodes.size(); return (int)jointNodes.size();
} }
@ -62,10 +59,11 @@ class FbxSkinningAccess {
const FbxAMatrix& GetInverseBindMatrix(const int jointIndex) const { const FbxAMatrix& GetInverseBindMatrix(const int jointIndex) const {
return inverseBindMatrices[jointIndex]; return inverseBindMatrices[jointIndex];
} }
const std::vector<FbxVertexSkinningInfo> GetVertexSkinningInfo(const int controlPointIndex) const { const std::vector<FbxVertexSkinningInfo> GetVertexSkinningInfo(
return vertexSkinning[controlPointIndex]; const int controlPointIndex) const {
} return vertexSkinning[controlPointIndex];
}
private: private:
int rootIndex; int rootIndex;

View File

@ -45,8 +45,7 @@ FbxMaterialsAccess::FbxMaterialsAccess(
continue; continue;
} }
auto* surfaceMaterial = auto* surfaceMaterial = mesh->GetNode()->GetSrcObject<FbxSurfaceMaterial>(materialNum);
mesh->GetNode()->GetSrcObject<FbxSurfaceMaterial>(materialNum);
if (!surfaceMaterial) { if (!surfaceMaterial) {
if (++warnMtrCount == 1) { if (++warnMtrCount == 1) {
@ -67,7 +66,6 @@ FbxMaterialsAccess::FbxMaterialsAccess(
userProperties.resize(materialNum + 1); userProperties.resize(materialNum + 1);
} }
if (surfaceMaterial && userProperties[materialNum].empty()) { if (surfaceMaterial && userProperties[materialNum].empty()) {
FbxProperty objectProperty = surfaceMaterial->GetFirstProperty(); FbxProperty objectProperty = surfaceMaterial->GetFirstProperty();
while (objectProperty.IsValid()) { while (objectProperty.IsValid()) {
if (objectProperty.GetFlag(FbxPropertyFlags::eUserDefined)) { if (objectProperty.GetFlag(FbxPropertyFlags::eUserDefined)) {

View File

@ -640,20 +640,21 @@ ModelData* Raw2Gltf(
for (int jj = 0; jj < surfaceModel.GetVertexCount(); jj++) { for (int jj = 0; jj < surfaceModel.GetVertexCount(); jj++) {
auto blendVertex = surfaceModel.GetVertex(jj).blends[channelIx]; auto blendVertex = surfaceModel.GetVertex(jj).blends[channelIx];
shapeBounds.AddPoint(blendVertex.position); shapeBounds.AddPoint(blendVertex.position);
bool isSparseVertex = options.disableSparseBlendShapes; // If sparse is off, add all vertices bool isSparseVertex =
options.disableSparseBlendShapes; // If sparse is off, add all vertices
// Check to see whether position, normal or tangent deviates from base mesh and flag as // Check to see whether position, normal or tangent deviates from base mesh and flag as
// sparse. // sparse.
if (blendVertex.position.Length() > 0.00) { if (blendVertex.position.Length() > 0.00) {
isSparseVertex = true; isSparseVertex = true;
} }
// if (options.useBlendShapeNormals && channel.hasNormals && // if (options.useBlendShapeNormals && channel.hasNormals &&
// blendVertex.normal.Length() > 0.00) { // blendVertex.normal.Length() > 0.00) {
// isSparseVertex = true; // isSparseVertex = true;
// } // }
// if (options.useBlendShapeTangents && channel.hasTangents && // if (options.useBlendShapeTangents && channel.hasTangents &&
// blendVertex.tangent.Length() > 0.00) { // blendVertex.tangent.Length() > 0.00) {
// isSparseVertex = true; // isSparseVertex = true;
// } // }
if (isSparseVertex == true) { if (isSparseVertex == true) {
sparseIndices.push_back(jj); sparseIndices.push_back(jj);
positions.push_back(blendVertex.position); positions.push_back(blendVertex.position);

View File

@ -213,7 +213,8 @@ std::shared_ptr<TextureData> TextureBuilder::simple(int rawTexIndex, const std::
} }
if (!image) { if (!image) {
// fallback is tiny transparent PNG // fallback is tiny transparent PNG
// image = new ImageData(textureName, "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="); // image = new ImageData(textureName,
// "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==");
return nullptr; return nullptr;
} }

View File

@ -27,11 +27,11 @@ class TextureBuilder {
const std::string& outputFolder, const std::string& outputFolder,
GltfModel& gltf) GltfModel& gltf)
: raw(raw), options(options), outputFolder(outputFolder), gltf(gltf) { : raw(raw), options(options), outputFolder(outputFolder), gltf(gltf) {
if (!outputFolder.empty()) { if (!outputFolder.empty()) {
if (outputFolder[outputFolder.size() - 1] == '/') { if (outputFolder[outputFolder.size() - 1] == '/') {
this->outputFolder = outputFolder.substr(0, outputFolder.size() - 1) ; this->outputFolder = outputFolder.substr(0, outputFolder.size() - 1);
}
} }
}
} }
~TextureBuilder() {} ~TextureBuilder() {}

View File

@ -18,7 +18,12 @@ AccessorData::AccessorData(const BufferViewData& bufferView, GLType type, std::s
name(name), name(name),
sparse(false) {} sparse(false) {}
AccessorData::AccessorData(const AccessorData& baseAccessor, const BufferViewData& sparseIdxBufferView, const BufferViewData& sparseDataBufferView, GLType type, std::string name) AccessorData::AccessorData(
const AccessorData& baseAccessor,
const BufferViewData& sparseIdxBufferView,
const BufferViewData& sparseDataBufferView,
GLType type,
std::string name)
: Holdable(), : Holdable(),
bufferView(baseAccessor.bufferView), bufferView(baseAccessor.bufferView),
type(std::move(type)), type(std::move(type)),
@ -51,12 +56,13 @@ json AccessorData::serialize() const {
} }
if (sparse) { if (sparse) {
json sparseData = {{"count", sparseIdxCount}}; json sparseData = {{"count", sparseIdxCount}};
sparseData["indices"] = { {"bufferView", sparseIdxBufferView}, sparseData["indices"] = {
{"byteOffset", sparseIdxBufferViewOffset}, {"bufferView", sparseIdxBufferView},
{"componentType", sparseIdxBufferViewType}}; {"byteOffset", sparseIdxBufferViewOffset},
{"componentType", sparseIdxBufferViewType}};
sparseData["values"] = { {"bufferView", sparseDataBufferView}, sparseData["values"] = {
{"byteOffset", sparseDataBufferViewOffset}}; {"bufferView", sparseDataBufferView}, {"byteOffset", sparseDataBufferViewOffset}};
result["sparse"] = sparseData; result["sparse"] = sparseData;
} }

View File

@ -11,30 +11,35 @@
#include "gltf/Raw2Gltf.hpp" #include "gltf/Raw2Gltf.hpp"
struct AccessorData : Holdable { struct AccessorData : Holdable {
AccessorData(const BufferViewData& bufferView, GLType type, std::string name); AccessorData(const BufferViewData& bufferView, GLType type, std::string name);
explicit AccessorData(GLType type); explicit AccessorData(GLType type);
AccessorData(const AccessorData& baseAccessor, const BufferViewData& sparseIdxBufferView, const BufferViewData& sparseDataBufferView, GLType type, std::string name); AccessorData(
const AccessorData& baseAccessor,
const BufferViewData& sparseIdxBufferView,
const BufferViewData& sparseDataBufferView,
GLType type,
std::string name);
json serialize() const override; json serialize() const override;
unsigned int byteLength() const { unsigned int byteLength() const {
return type.byteStride() * count; return type.byteStride() * count;
} }
const int bufferView; const int bufferView;
const GLType type; const GLType type;
unsigned int byteOffset; unsigned int byteOffset;
unsigned int count; unsigned int count;
std::vector<float> min; std::vector<float> min;
std::vector<float> max; std::vector<float> max;
std::string name; std::string name;
bool sparse; bool sparse;
int sparseIdxCount; int sparseIdxCount;
int sparseIdxBufferView; int sparseIdxBufferView;
int sparseIdxBufferViewOffset; int sparseIdxBufferViewOffset;
int sparseIdxBufferViewType; int sparseIdxBufferViewType;
int sparseDataBufferView; int sparseDataBufferView;
int sparseDataBufferViewOffset; int sparseDataBufferViewOffset;
}; };

View File

@ -38,11 +38,12 @@ AnimationData::channel_t::channel_t(uint32_t ix, const NodeData& node, std::stri
AnimationData::sampler_t::sampler_t(uint32_t time, uint32_t output) : time(time), output(output) {} AnimationData::sampler_t::sampler_t(uint32_t time, uint32_t output) : time(time), output(output) {}
void to_json(json& j, const AnimationData::channel_t& data) { void to_json(json& j, const AnimationData::channel_t& data) {
j = json{{"sampler", data.ix}, j = json{
{ {"sampler", data.ix},
"target", {
{{"node", data.node}, {"path", data.path}}, "target",
}}; {{"node", data.node}, {"path", data.path}},
}};
} }
void to_json(json& j, const AnimationData::sampler_t& data) { void to_json(json& j, const AnimationData::sampler_t& data) {

View File

@ -92,12 +92,13 @@ MaterialData::MaterialData(
pbrMetallicRoughness(pbrMetallicRoughness) {} pbrMetallicRoughness(pbrMetallicRoughness) {}
json MaterialData::serialize() const { json MaterialData::serialize() const {
json result = {{"name", name}, json result = {
{"alphaMode", isTransparent ? "BLEND" : "OPAQUE"}, {"name", name},
{"extras", {"alphaMode", isTransparent ? "BLEND" : "OPAQUE"},
{{"fromFBX", {"extras",
{{"shadingModel", Describe(shadingModel)}, {{"fromFBX",
{"isTruePBR", shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH}}}}}}; {{"shadingModel", Describe(shadingModel)},
{"isTruePBR", shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH}}}}}};
if (normalTexture != nullptr) { if (normalTexture != nullptr) {
result["normalTexture"] = *normalTexture; result["normalTexture"] = *normalTexture;

View File

@ -73,7 +73,8 @@ void to_json(json& j, const PrimitiveData& d) {
j["targets"] = targets; j["targets"] = targets;
} }
if (!d.dracoAttributes.empty()) { if (!d.dracoAttributes.empty()) {
j["extensions"] = {{KHR_DRACO_MESH_COMPRESSION, j["extensions"] = {
{{"bufferView", d.dracoBufferView}, {"attributes", d.dracoAttributes}}}}; {KHR_DRACO_MESH_COMPRESSION,
{{"bufferView", d.dracoBufferView}, {"attributes", d.dracoAttributes}}}};
} }
} }

View File

@ -60,15 +60,17 @@ struct PrimitiveData {
} }
template <class T> template <class T>
void AddDracoArrayAttrib(const AttributeArrayDefinition<T> attribute, const std::vector<T>& attribArr) { void AddDracoArrayAttrib(
const AttributeArrayDefinition<T> attribute,
const std::vector<T>& attribArr) {
draco::PointAttribute att; draco::PointAttribute att;
int8_t componentCount = attribute.glType.count; int8_t componentCount = attribute.glType.count;
att.Init( att.Init(
attribute.dracoAttribute, attribute.dracoAttribute,
componentCount, componentCount,
attribute.dracoComponentType, attribute.dracoComponentType,
false, false,
componentCount * draco::DataTypeLength(attribute.dracoComponentType)); componentCount * draco::DataTypeLength(attribute.dracoComponentType));
const int dracoAttId = dracoMesh->AddAttribute(att, true, attribArr.size()); const int dracoAttId = dracoMesh->AddAttribute(att, true, attribArr.size());
draco::PointAttribute* attPtr = dracoMesh->attribute(dracoAttId); draco::PointAttribute* attPtr = dracoMesh->attribute(dracoAttId);

View File

@ -21,7 +21,8 @@ SkinData::SkinData(
skeletonRootNode(skeletonRootNode.ix) {} skeletonRootNode(skeletonRootNode.ix) {}
json SkinData::serialize() const { json SkinData::serialize() const {
return {{"joints", joints}, return {
{"inverseBindMatrices", inverseBindMatrices}, {"joints", joints},
{"skeleton", skeletonRootNode}}; {"inverseBindMatrices", inverseBindMatrices},
{"skeleton", skeletonRootNode}};
} }

View File

@ -34,9 +34,8 @@ size_t VertexHasher::operator()(const RawVertex& v) const {
bool RawVertex::operator==(const RawVertex& other) const { bool RawVertex::operator==(const RawVertex& other) const {
return (position == other.position) && (normal == other.normal) && (tangent == other.tangent) && return (position == other.position) && (normal == other.normal) && (tangent == other.tangent) &&
(binormal == other.binormal) && (color == other.color) && (uv0 == other.uv0) && (binormal == other.binormal) && (color == other.color) && (uv0 == other.uv0) &&
(uv1 == other.uv1) && (uv1 == other.uv1) && (jointWeights == other.jointWeights) &&
(jointWeights == other.jointWeights) && (jointIndices == other.jointIndices) && (jointIndices == other.jointIndices) && (polarityUv0 == other.polarityUv0) &&
(polarityUv0 == other.polarityUv0) &&
(blendSurfaceIx == other.blendSurfaceIx) && (blends == other.blends); (blendSurfaceIx == other.blendSurfaceIx) && (blends == other.blends);
} }
@ -64,13 +63,13 @@ size_t RawVertex::Difference(const RawVertex& other) const {
attributes |= RAW_VERTEX_ATTRIBUTE_UV1; attributes |= RAW_VERTEX_ATTRIBUTE_UV1;
} }
// Always need both or neither. // Always need both or neither.
if (jointIndices != other.jointIndices || jointWeights != other.jointWeights) { if (jointIndices != other.jointIndices || jointWeights != other.jointWeights) {
attributes |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; attributes |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
} }
return attributes; return attributes;
} }
RawModel::RawModel() : vertexAttributes(0){} RawModel::RawModel() : vertexAttributes(0) {}
void RawModel::AddVertexAttribute(const RawVertexAttribute attrib) { void RawModel::AddVertexAttribute(const RawVertexAttribute attrib) {
vertexAttributes |= attrib; vertexAttributes |= attrib;
@ -406,15 +405,17 @@ void RawModel::Condense(const int maxSkinningWeights, const bool normalizeWeight
{ {
globalMaxWeights = 0; globalMaxWeights = 0;
for (auto& vertex: vertices) { for (auto& vertex : vertices) {
// Sort from largest to smallest weight. // Sort from largest to smallest weight.
std::sort(vertex.skinningInfo.begin(), vertex.skinningInfo.end(), std::greater<RawVertexSkinningInfo>()); std::sort(
vertex.skinningInfo.begin(),
vertex.skinningInfo.end(),
std::greater<RawVertexSkinningInfo>());
// Reduce to fit the requirements. // Reduce to fit the requirements.
if (maxSkinningWeights < vertex.skinningInfo.size()) if (maxSkinningWeights < vertex.skinningInfo.size())
vertex.skinningInfo.resize(maxSkinningWeights); vertex.skinningInfo.resize(maxSkinningWeights);
globalMaxWeights = std::max(globalMaxWeights, (int) vertex.skinningInfo.size()); globalMaxWeights = std::max(globalMaxWeights, (int)vertex.skinningInfo.size());
// Normalize weights if requested. // Normalize weights if requested.
if (normalizeWeights) { if (normalizeWeights) {
@ -432,15 +433,15 @@ void RawModel::Condense(const int maxSkinningWeights, const bool normalizeWeight
AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS); AddVertexAttribute(RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS);
} }
assert(globalMaxWeights >= 0); assert(globalMaxWeights >= 0);
// Copy to gltf friendly structure // Copy to gltf friendly structure
for (auto& vertex : vertices) { for (auto& vertex : vertices) {
vertex.jointIndices.reserve(globalMaxWeights); vertex.jointIndices.reserve(globalMaxWeights);
vertex.jointWeights.reserve(globalMaxWeights); vertex.jointWeights.reserve(globalMaxWeights);
for (int i = 0; i < globalMaxWeights; i += 4) { // ensure every vertex has the same amount of weights for (int i = 0; i < globalMaxWeights;
i += 4) { // ensure every vertex has the same amount of weights
Vec4f weights{0.0}; Vec4f weights{0.0};
Vec4i jointIds{0,0,0,0}; Vec4i jointIds{0, 0, 0, 0};
for (int j = i; j < i + 4 && j < vertex.skinningInfo.size(); j++) { for (int j = i; j < i + 4 && j < vertex.skinningInfo.size(); j++) {
weights[j - i] = vertex.skinningInfo[j].jointWeight; weights[j - i] = vertex.skinningInfo[j].jointWeight;
jointIds[j - i] = vertex.skinningInfo[j].jointIndex; jointIds[j - i] = vertex.skinningInfo[j].jointIndex;
@ -668,11 +669,11 @@ void RawModel::CreateMaterialModels(
if ((keep & RAW_VERTEX_ATTRIBUTE_UV1) == 0) { if ((keep & RAW_VERTEX_ATTRIBUTE_UV1) == 0) {
vertex.uv1 = defaultVertex.uv1; vertex.uv1 = defaultVertex.uv1;
} }
if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) == 0) { if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) == 0) {
vertex.jointIndices = defaultVertex.jointIndices; vertex.jointIndices = defaultVertex.jointIndices;
} }
if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) == 0) { if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) == 0) {
vertex.jointWeights = defaultVertex.jointWeights; vertex.jointWeights = defaultVertex.jointWeights;
} }
} }

View File

@ -22,7 +22,7 @@ enum RawVertexAttribute {
RAW_VERTEX_ATTRIBUTE_COLOR = 1 << 4, RAW_VERTEX_ATTRIBUTE_COLOR = 1 << 4,
RAW_VERTEX_ATTRIBUTE_UV0 = 1 << 5, RAW_VERTEX_ATTRIBUTE_UV0 = 1 << 5,
RAW_VERTEX_ATTRIBUTE_UV1 = 1 << 6, RAW_VERTEX_ATTRIBUTE_UV1 = 1 << 6,
RAW_VERTEX_ATTRIBUTE_JOINT_INDICES = 1 << 7, RAW_VERTEX_ATTRIBUTE_JOINT_INDICES = 1 << 7,
RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS = 1 << 8, RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS = 1 << 8,
RAW_VERTEX_ATTRIBUTE_AUTO = 1 << 31 RAW_VERTEX_ATTRIBUTE_AUTO = 1 << 31
@ -38,8 +38,7 @@ struct RawBlendVertex {
} }
}; };
struct RawVertexSkinningInfo struct RawVertexSkinningInfo {
{
int jointIndex; int jointIndex;
float jointWeight; float jointWeight;
@ -58,7 +57,7 @@ struct RawVertex {
Vec2f uv1{0.0f}; Vec2f uv1{0.0f};
std::vector<Vec4i> jointIndices; std::vector<Vec4i> jointIndices;
std::vector<Vec4f> jointWeights; std::vector<Vec4f> jointWeights;
std::vector<RawVertexSkinningInfo> skinningInfo; std::vector<RawVertexSkinningInfo> skinningInfo;
// end of members that directly correspond to vertex attributes // end of members that directly correspond to vertex attributes
@ -359,7 +358,6 @@ struct RawNode {
class RawModel { class RawModel {
public: public:
RawModel(); RawModel();
// Add geometry. // Add geometry.
@ -437,8 +435,8 @@ class RawModel {
int GetVertexCount() const { int GetVertexCount() const {
return (int)vertices.size(); return (int)vertices.size();
} }
int GetGlobalWeightCount() const{ int GetGlobalWeightCount() const {
return globalMaxWeights; return globalMaxWeights;
} }
@ -527,10 +525,10 @@ class RawModel {
// Create individual attribute arrays, with the source as an array. // Create individual attribute arrays, with the source as an array.
// Returns true if the vertices store the particular attribute. // Returns true if the vertices store the particular attribute.
template <typename _attrib_type_> template <typename _attrib_type_>
void GetArrayAttributeArray(std::vector<_attrib_type_>& out, void GetArrayAttributeArray(
const std::vector<_attrib_type_> RawVertex::*ptr, std::vector<_attrib_type_>& out,
const int arrayOffset) const std::vector<_attrib_type_> RawVertex::*ptr,
const; const int arrayOffset) const;
// Create an array with a raw model for each material. // Create an array with a raw model for each material.
// Multiple surfaces with the same material will turn into a single model. // Multiple surfaces with the same material will turn into a single model.
@ -571,9 +569,9 @@ void RawModel::GetAttributeArray(
template <typename _attrib_type_> template <typename _attrib_type_>
void RawModel::GetArrayAttributeArray( void RawModel::GetArrayAttributeArray(
std::vector<_attrib_type_>& out, std::vector<_attrib_type_>& out,
const std::vector<_attrib_type_> RawVertex::*ptr, const std::vector<_attrib_type_> RawVertex::*ptr,
const int arrayOffset) const { const int arrayOffset) const {
out.resize(vertices.size()); out.resize(vertices.size());
for (size_t i = 0; i < vertices.size(); i++) { for (size_t i = 0; i < vertices.size(); i++) {
out[i] = (vertices[i].*ptr)[arrayOffset]; out[i] = (vertices[i].*ptr)[arrayOffset];