I have written a simple obj parser in c++ that loads the vertices, indices and texture coordinates (that's all the data I need).
Here is the function:
Model* ModelLoader::loadFromOBJ(string objFile, ShaderProgram *shader, GLuint texture)
{
fstream file;
file.open(objFile);
if (!file.is_open())
{
cout << "ModelLoader: " << objFile << " was not found";
return NULL;
}
int vertexCount = 0;
int indexCount = 0;
vector<Vector3> vertices;
vector<Vector2> textureCoordinates;
vector<Vector2> textureCoordinatesFinal;
vector<unsigned int> vertexIndices;
vector<unsigned int> textureIndices;
string line;
while (getline(file, line))
{
vector<string> splitLine = Common::splitString(line, ' ');
// v - vertex
if (splitLine[0] == "v")
{
Vector3 vertex(stof(splitLine[1]), stof(splitLine[2]), stof(splitLine[3]));
vertices.push_back(vertex);
vertexCount++;
}
// vt - texture coordinate
else if (splitLine[0] == "vt")
{
Vector2 textureCoordinate(stof(splitLine[1]), 1 - stof(splitLine[2]));
textureCoordinates.push_back(textureCoordinate);
}
// f - face
else if (splitLine[0] == "f")
{
vector<string> faceSplit1 = Common::splitString(splitLine[1], '/');
vector<string> faceSplit2 = Common::splitString(splitLine[2], '/');
vector<string> faceSplit3 = Common::splitString(splitLine[3], '/');
unsigned int vi1 = stoi(faceSplit1[0]) - 1;
unsigned int vi2 = stoi(faceSplit2[0]) - 1;
unsigned int vi3 = stoi(faceSplit3[0]) - 1;
unsigned int ti1 = stoi(faceSplit1[1]) - 1;
unsigned int ti2 = stoi(faceSplit2[1]) - 1;
unsigned int ti3 = stoi(faceSplit3[1]) - 1;
vertexIndices.push_back(vi1);
vertexIndices.push_back(vi2);
vertexIndices.push_back(vi3);
textureIndices.push_back(ti1);
textureIndices.push_back(ti2);
textureIndices.push_back(ti3);
indexCount += 3;
}
}
// rearanging textureCoordinates into textureCoordinatesFinal based on textureIndices
for (int i = 0; i < indexCount; i++)
textureCoordinatesFinal.push_back(textureCoordinates[textureIndices[i]]);
Model *result = new Model(shader, vertexCount, &vertices[0], NULL, texture, indexCount, &textureCoordinatesFinal[0], &vertexIndices[0]);
models.push_back(result);
return result;
}
As you can see, I take into account the 1 - texCoord.y (because blender and opengl use a different coordinate system for textures).
I also arrange the texture coordinates based on the texture indices after the while loop.
However, the models I try to render have their textures messed up. Here is an example:
Texture messed up
I even tried it with a single cube which I unwrapped myself in blender and applied a very simple brick texture. In 1 or 2 faces, the texture was fine and working, then in some other faces, 1 of the tringles had a correct texture and the others appeared streched out (same as in the picture above).
To define a mesh, there is only one index list that indexes the vertex attributes. The vertex attributes (in your case the vertices and the texture coordinate) form a record set, which is referred by these indices.
This causes, that each vertex coordinate may occur several times in the list and each texture coordinate may occur several times in the list. But each combination of vertices and texture coordinates is unique.
Take the vertexIndices and textureIndices an create unique pairs of vertices and texture coordinates (verticesFinal, textureCoordinatesFinal).
Create new attribute_indices, which indexes the pairs.
Use the a temporary container attribute_pairs to manage the unique pairs and to identify their indices:
#include <vector>
#include <map>
// input
std::vector<Vector3> vertices;
std::vector<Vector2> textureCoordinates;
std::vector<unsigned int> vertexIndices;
std::vector<unsigned int> textureIndices;
std::vector<unsigned int> attribute_indices; // final indices
std::vector<Vector3> verticesFinal; // final vertices buffer
std::vector<Vector2> textureCoordinatesFinal; // final texture coordinate buffer
// map a pair of indices to the final attribute index
std::map<std::pair<unsigned int, unsigned int>, unsigned int> attribute_pairs;
// vertexIndices.size() == textureIndices.size()
for ( size_t i = 0; i < vertexIndices.size(); ++ i )
{
// pair of vertex index an texture index
auto attr = std::make_pair( vertexIndices[i], textureIndices[i] );
// check if the pair aready is a member of "attribute_pairs"
auto attr_it = attribute_pairs.find( attr );
if ( attr_it == attribute_pairs.end() )
{
// "attr" is a new pair
// add the attributes to the final buffers
verticesFinal.push_back( vertices[attr.first] );
textureCoordinatesFinal.push_back( textureCoordinates[attr.first] );
// the new final index is the next index
unsigned int new_index = (unsigned int)attribute_pairs.size();
attribute_indices.push_back( new_index );
// add the new map entry
attribute_pairs[attr] = new_index;
}
else
{
// the pair "attr" already exists: add the index which was found in the map
attribute_indices.push_back( attr_it->second );
}
}
Note the number of the vertex coordinates (verticesFinal.size()) is equal the number of the texture coordiantes (textureCoordinatesFinal.size()). But the number of the indices (attribute_indices.size()) is something completely different.
// verticesFinal.size() == textureCoordinatesFinal.size()
Model *result = new Model(
shader,
verticesFinal.size(),
verticesFinal.data(),
NULL, texture,
attribute_indices.size(),
textureCoordinatesFinal.data(),
attribute_indices.data() );
Related
I have a problem with creating a set of unique vertices containing Position Coordinate, Texture Coordinade and Normals. I decided to use std::set for this kind of problem. The problem is that it seems that it somehow does not creating the correct set of vertices. The data I loaded from .obj file 100% correct as it is renders the mesh as I expected. But new set of data is just creates a blob of triangles.
Correct result using only Positions of vertices, expected result:
And generated data:
The code routine:
struct vertex
{
glm::vec3 Position;
glm::vec2 TextureCoord;
glm::vec3 Normal;
bool operator==(const vertex& rhs) const
{
bool Res1 = this->Position == rhs.Position;
bool Res2 = this->TextureCoord == rhs.TextureCoord;
bool Res3 = this->Normal == rhs.Normal;
return Res1 && Res2 && Res3;
}
};
namespace std
{
template<>
struct hash<vertex>
{
size_t operator()(const vertex& VertData) const
{
size_t res = 0;
const_cast<hash<vertex>*>(this)->hash_combine(res, hash<glm::vec3>()(VertData.Position));
const_cast<hash<vertex>*>(this)->hash_combine(res, hash<glm::vec2>()(VertData.TextureCoord));
const_cast<hash<vertex>*>(this)->hash_combine(res, hash<glm::vec3>()(VertData.Normal));
return res;
}
private:
// NOTE: got from glm library
void hash_combine(size_t& seed, size_t hash)
{
hash += 0x9e3779b9 + (seed << 6) + (seed >> 2);
seed ^= hash;
}
};
}
void mesh::BuildUniqueVertices()
{
std::unordered_set<vertex> UniqueVertices;
//std::unordered_map<u32, vertex> UniqueVertices;
u32 IndexCount = CoordIndices.size();
std::vector<u32> RemapedIndices(IndexCount);
for(u32 VertexIndex = 0;
VertexIndex < IndexCount;
++VertexIndex)
{
vertex Vert = {};
v3 Pos = Coords[CoordIndices[VertexIndex]];
Vert.Position = glm::vec3(Pos.x, Pos.y, Pos.z);
if(NormalIndices.size() != 0)
{
v3 Norm = Normals[NormalIndices[VertexIndex]];
Vert.Normal = glm::vec3(Norm.x, Norm.y, Norm.z);
}
if(TextCoords.size() != 0)
{
v2 TextCoord = TextCoords[TextCoordIndices[VertexIndex]];
Vert.TextureCoord = glm::vec2(TextCoord.x, TextCoord.y);
}
// NOTE: think about something faster
auto Hint = UniqueVertices.insert(Vert);
if (Hint.second)
{
RemapedIndices[VertexIndex] = VertexIndex;
}
else
{
RemapedIndices[VertexIndex] = static_cast<u32>(std::distance(UniqueVertices.begin(), UniqueVertices.find(Vert)));
}
}
VertexIndices = std::move(RemapedIndices);
Vertices.reserve(UniqueVertices.size());
for(auto it = UniqueVertices.begin();
it != UniqueVertices.end();)
{
Vertices.push_back(std::move(UniqueVertices.extract(it++).value()));
}
}
What the error could be. I am suspecting that hash function is not doing its job right and therefor I am getting really trashed results of random triangles around initial mesh bounds. Thanks.
This construction shows that you're going down the wrong path:
RemapedIndices[VertexIndex] = static_cast<u32>(std::distance(UniqueVertices.begin(), UniqueVertices.find(Vert)));
It's clear that you want the "offset" of the vertex within the UniqueVertices but you're modifying the container in the same loop with UniqueVertices.insert(Vert);. That means that the std::distance result you calculate is potentially invalidated by the next loop. Every index except the very last one you calculate will potentially be garbage by the time you finish the outer loop.
Just use a vector<vertex> and stop trying to de-dupe vertices. If you later find you need to optimize for better performance, you're going to be better off trying to make sure that the mesh is optimized for cache locality.
If you really, really feel the need to dedupe, you need to do it in a multi-step process, something like this...
First map the original indices to some kind of unique key (NOT the full vertex... you want something that can be used as both a map key and value, so has good hashing and equality functionality, like std::string). For instance, you could take the 3 source indices and convert them to fixed length hex strings concatenated together. Just make sure the transformation is reversible. Here I've stubbed it out wit std::string to_key(uint32_t CoordIndex, uint32_t NormalIndex, uint32_t TextCoordIndex)
std::unordered_map<uint32_t, std::string> originalIndexToKey;
std::unordered_set<std::string> uniqueKeys;
uint32_t IndexCount = CoordIndices.size();
for (u32 VertexIndex = 0; VertexIndex < IndexCount; ++VertexIndex) {
uint32_t CoordIndex = UINT32_MAX, NormalIndex = UINT32_MAX, TextCoordIndex = UINT32_MAX;
CoordIndex = CoordIndices[VertexIndex];
if (NormalIndices.size() != 0) {
NormalIndex = NormalIndices[VertexIndex];
}
if (TextCoords.size() != 0) {
TextCoordIndex = TextCoordIndices[VertexIndex];
}
std::string key = to_key(CoordIndex, NormalIndex, TextCoordIndex);
originalIndexToKey.insert(VertexIndex, key);
uniqueKeys.insert(key);
}
Next, take all the unique keys and construct the unique vertices with them in a vector, so they have fixed positions. Here you need to get the sub-indices back from the key with a void from_key(const std::string& key, uint32_t & CoordIndex, uint32_t & NormalIndex, uint32_t & TextCoordIndex) function.
std::unordered_map<std::string, uint32_t> keyToNewIndex;
std::vector<vertex> uniqueVertices;
for (const auto& key : uniqueKeys) {
uint32_t NewIndex = uniqueVertices.size();
keyToNewIndex.insert(key, NewIndex)
// convert the key back into 3 indices
uint32_t CoordIndex, NormalIndex, TextCoordIndex;
from_key(key, CoordIndex, NormalIndex, TextCoordIndex);
vertex Vert = {};
v3 Pos = Coords[CoordIndex];
Vert.Position = glm::vec3(Pos.x, Pos.y, Pos.z);
if (NormalIndex != UINT32_MAX) {
v3 Norm = Normals[NormalIndices[VertexIndex]];
Vert.Normal = glm::vec3(Norm.x, Norm.y, Norm.z);
}
if (TextCoordIndex != UINT32_MAX) {
v2 TextCoord = TextCoords[TextCoordIndices[VertexIndex]];
Vert.TextureCoord = glm::vec2(TextCoord.x, TextCoord.y);
}
uniqueVertices.push_back(Vert);
}
Finally, you need to map from the original index out to the new index, preserving the triangle geometry.
std::vector<uint32_t> RemapedIndices;
RemapedIndices.reserve(IndexCount);
for(u32 OriginalVertexIndex = 0; OriginalVertexIndex < IndexCount; ++OriginalVertexIndex) {
auto key = originalIndexToKey[OriginalVertexIndex];
auto NewIndex = keyToNewIndex[key];
RemapedIndices.push_back(NewIndex );
}
The image is not completely randomly messed up. If you combine the two images, these fit perfectly:
The only problem, a mesh means triangles. You tell Vulkan how to draw each triangle. Having a mesh composed of 8 triangles, 9 unique vertices, you pass vertices for each one of 8 triangles separately, see screenshot below:
1:[1,2,4], 2:[2,5,4], 3:[2,3,5], 4:[3,6,5], 5:[4,5,7], 6:[5,8,7], 7:[5,6,8], 8:[6,9,8] (see red numbers).
So the vertex 5 will be repeated 6 times, because there the triangles 2,3,4,5,6,7 shares the vertex 5. Same goes for normals and textures. The vast majority of vertices with all set coordinates, normals, textures, colors, will be repeated exactly 6 times. If it is double coated it will be repeated 12 times. The boundary vertices will be repeated 3 and respectively 6 times if double coated. And the edge vertices will be repeated 1/2 times, see left and right edge, and respectively 2/4 times if double coated:
If you try to remove the duplicates, you transform a mesh in a mess. And this is regardless of how well these are sorted or unsorted. The order makes no more any sense.
If you want to use unique set of coordinates, there probably exists in Vulkan some technique to add an index buffer, similar to OpenGL. So you can pass unique coordinates, but build correctly the index array. All vertices will be unique, but the indexes in index array will not.
We are building a basic game engine with a friend of mine as part of a course, and we are now in the process of implementing a grid-map. The grid map will act as a dev-tool for checking the movement filtering logic that is based on tile type i.e., SOLID, EMPTY, etc.
We are using the Tiled editor to extract our tile sets and our map info in the form of .csv and .json files. We have managed to load, construct and render the tile map successfully while also using memoization for the tile indices. Next up, we must load, construct and render a grid map (4×4 per tile) based on the tile indices that we got from Tiled.
I'm trying to figure out the logic for this process, and here is what I have so far. Please correct me if I'm wrong:
Somehow, determine the various tile types dynamically for the various tile sets that the user/dev may want to use with the engine
While parsing the tile indices and constructing the tile map, generate the 4×4 grid element for each tile. While doing this, keep track of each grid element's associated tile index, its position within the tile & its type.
Generate a generic texture for the “solid” tiles that will be rendered on top of the actual tile of the tile map (i.e., red outline and transparent center)
Render the tiles from (3) on top of the current tile map.
Here come the questions:
— Is there a setting in Tiled that I'm not aware of that can generate the tile types based on their index so that we can achieve (1) dynamically and allow the user/dev to provide any tile set? If not, is the only other way to manually review the tile set and determine the mappings? For example, if we have 30 unique tiles, would the only option be to manually write a map in order to store the tile's Index and its respective type?
— Regarding (2), in our lectures we saw a naive example of how we can use the grid elements (16 per tile) to detect collisions and filter movement. That example included parsing each pixel of each tile, extracting the color percentages and determining if that segment of the tile is a solid or not (i.e., mostly white means empty). Just want to confirm my assumption that this seems like an awful solution for determining which parts of a tile should be collide-able, right?
— For the rendering part, assuming that we have already rendered the tile map on the window's surface, should we use SDL_RenderCopy to draw the grid element outlines over the solid tiles?
Here's our tile map parsing (based on the .csv file) & memoization logic:
TileMap class:
#define MAX_WIDTH 192
#define MAX_HEIGHT 336
typedef unsigned short Dim;
typedef short Index;
typedef unsigned char byte;
typedef struct {
Dim x, y;
} Tile;
class TileMap {
public:
explicit TileMap(const std::string& mapPath);
std::vector<std::string> tilesetPaths;
std::vector<std::string> csvLayers;
std::map <Index, std::pair <unsigned short, unsigned short> > preCachedIndices;
std::vector<Index> indices;
unsigned short tilesetHeight{}, tilesetWidth{}; // number of tile rows/columns
unsigned short tileHeight, tileWidth{};
unsigned short mapWidth{}, mapHeight{};
Tile GetTile(Dim row, Dim col);
void WriteTextMap(const Json::Value& data, const std::string& fp);
bool preCacheIndices(const std::string &inputMapPath);
void renderMap(SDL_Window *window, SDL_Rect &cameraOffset);
void printMapInfo();
void initMapInfo(const std::string& path);
};
parsing & memoization logic:
bool TileMap::preCacheIndices(const std::string &inputMapPath) {
std::ifstream file;
std::string line;
file.open(inputMapPath.c_str(), std::ios::in);
if (!file) {
std::cerr << "Error opening file: " << std::strerror(errno) << "\n";
return false;
}
while (std::getline(file, line)) {
std::stringstream ss(line);
std::string item;
while (std::getline(ss, item, ',')) {
Index index = std::stoi(item);
indices.push_back(index);
// apply memoization to speed the process up (O(1) access time for already existing keys)
if (preCachedIndices.find(index) == preCachedIndices.end()) { // index has not been cached yet
unsigned short row = index / tilesetWidth;
unsigned short col = index % tilesetWidth;
std::pair<unsigned short, unsigned short> p = std::pair<unsigned short, unsigned short>(row, col);
preCachedIndices.insert(std::pair<Index, std::pair<unsigned short, unsigned short>>{index, p});
} else {
continue;
}
}
}
file.close();
return true;
}
rendering logic for the tile map:
void TileMap::renderMap(SDL_Window *window, SDL_Rect &cameraOffset) {
SDL_Surface *screenSurface, *tileset;
screenSurface = SDL_GetWindowSurface(window);
if (!screenSurface) {
printf("Could not initialize screen surface. Error message: %s", SDL_GetError());
}
tileset = IMG_Load(tilesetPaths.at(0).c_str()); // load tileset bitmap
SDL_FillRect(screenSurface, nullptr, SDL_MapRGB(screenSurface->format, 0xFF, 0xFF, 0xFF));
int row = 0;
int col = 0;
for (const auto &index: indices) {
SDL_Rect src, dest;
src.x = preCachedIndices.at(index).second * tileWidth;
src.y = preCachedIndices.at(index).first * tileHeight;
src.w = tileWidth; //tileManager.tilemapWidth;
src.h = tileHeight; //tileManager.tilemapHeight;
dest.x = col * tileWidth + cameraOffset.x;
dest.y = row * tileHeight - cameraOffset.y;
dest.w = tileWidth;
dest.h = tileHeight;
SDL_BlitSurface(tileset, &src, screenSurface, &dest);
if (col == mapWidth - 1) {
col = 0;
row++;
} else {
col++;
}
}
SDL_FreeSurface(tileset);
SDL_UpdateWindowSurface(window);
}
Grid map class:
typedef enum {
ThinAir,
LeftSolid,
RightSolid,
TopSolid,
BottomSolid,
Ground,
Floating,
} Masks;
typedef struct {
int x, y;
} Position;
//Operator overloading sto std::map can understand how to handle Position structs
inline bool operator<(Position const &a, Position const &b) {
return std::tie(a.x, a.y) < std::tie(b.x, b.y);
}
typedef struct {
Index index;
Masks mask;
} GridTile;
using GridIndex = byte;
class GridMap {
private:
TileMap *mapManager;
std::map<Position, GridTile> gridTiles;
unsigned short gridTileWidth, gridTileHeight;
public:
explicit GridMap(TileMap *mapManager);
void printGridTiles();
void precacheGridTiles();
};
The calculation for the grid tile width and height is done as follows:
gridTileHeight = mapManager->tileHeight / 4;
gridTileWidth = mapManager->tileWidth / 4;
My friend's approach to generating the grid map tiles so far:
void GridMap::precacheGridTiles() {
SDL_Surface *tileset;
//todo: This should change for layering (at(0) part)
tileset = IMG_Load(mapManager->tilesetPaths.at(0).c_str());
Index i = 0;
int row = 0, col = 0;
for (const auto &index: mapManager->indices) {
for (int j = 0; j < gridTileWidth * gridTileHeight; j++) {
GridTile gridTile;
gridTile.index = i;
//todo: Find way to determine mask
if(index == 61) {
//todo:sp check based on current position if the tile is top,left,right,bottom side
} else {
gridTile.mask = ThinAir;
}
Position position;
position.x = col + (j * gridTileWidth);
position.y = row + (j * gridTileHeight);
gridTiles[position] = gridTile;
}
if (col < mapManager->mapWidth) {
col += gridTileWidth;
} else {
col = 0;
row += gridTileHeight;
}
}
}
Any pointers for direction would be greatly appreciated. We're looking to confirm/adjust our logic and hopefully find an answer to our questions, but don't want an answer that solves the problem for us please.
I have the following code from learnopengl.com
void Model::load_model(string path)
{
//read file via ASSIMP
Assimp::Importer Importer;
const aiScene* scene = Importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);
//check for errors
if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)// if not zero
{
cout << "error, assimp ," << Importer.GetErrorString() << endl;
return;
}
//retrieve the directory path of the filepath
directory = path.substr(0, path.find_first_of('/'));
process_node(scene->mRootNode, scene);
}
/*
* Process a node in a recursive fashion . Process each individual mesh located at the node and repeat this process on its children nodes (if any)
*/
void Model::process_node(aiNode* node, const aiScene* scene)
{
for( GLuint i = 0; i < node->mNumMeshes; i++ )
{
//the node object only contains indices to index the actual objects of the scene.
//The scene contains all the data , node is just to keep stuff organized( like relations between nodes )
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
meshes.push_back(process_mesh(mesh, scene));
}
//after we've processed all the meshes ( if any ) we then recusrsively process each of the children nodes
for(GLuint i = 0; i < node->mNumChildren; i++)
{
process_node(node->mChildren[i], scene);
}
}
Mesh Model::process_mesh(aiMesh* mesh, const aiScene* scene)
{
//data to fill
vector<Mesh::Vertex> vertices;
vector<GLuint> indices;
vector<Mesh::Texture> textures;
//walk through each of the meshes vertices
for(GLuint i = 0; i < mesh->mNumVertices; i++)
{
Mesh::Vertex vertex;
// we declare a placeholder vector since assimp uses its own vector class that doesn't directly convert to glm's vec3 class
//so we transfter the data to this placeholder glm::vec3 first
glm::vec3 vector;
//positions
vector.x = mesh->mVertices[i].x;
vector.y = mesh->mVertices[i].y;
vector.z = mesh->mVertices[i].z;
vertex.position = vector;
//normals
vector.x = mesh->mNormals[i].x;
vector.y = mesh->mNormals[i].y;
vector.z = mesh->mNormals[i].z; ...
When I print out the first 20 values of mesh->mVertices[i].x y and z I get some with values greater than 1 as show below
x1: 1.58967 Y1: -0.618526 z1: -0.683333
x1: 1.58939 Y1: -0.626895 z1: -0.681676
The obj file I am importing doesn't have any values greater than 1 and this is making the render fail. Where might the problem be?
The positions of a model are related to its model coordinate system. So they can get bigger and less than 1, -1 or whatever. OpenGL is not limited to the range of [0 ... 1] for any values. So when your renderer needs the range from 0 to 1 you need to:
Find the min and max for your coordinates
Rescale all coordinates:
float scale = 1.0/(max - min);
foreach ( v : mesh.Vertices)
v=v*scale;
Or rewrite your renderer that he is able to render arbitrary coorinate.
I am currently trying to load an FBX mesh for use with DirectX, but my FBX file has it's UVs stored by vert index and the normals stored by control point index. How do I know which vertexes have which control point's values?
My code for loading positions, uvs and normals is ripped straight from the fbx example code, but I can post it if needed.
edit: as requested, here are the parts of my code I am talking about.
The UV code will go into the if statement for mapping mode by vert index, while the normal code is set to mapping mode by ctrl point
//load uvs
if (lUVElement->GetMappingMode() == FbxGeometryElement::eByControlPoint)
{
for (int lPolyIndex = 0; lPolyIndex < lPolyCount; ++lPolyIndex)
{
// build the max index array that we need to pass into MakePoly
const int lPolySize = mesh->GetPolygonSize(lPolyIndex);
for (int lVertIndex = 0; lVertIndex < lPolySize; ++lVertIndex)
{
//get the index of the current vertex in control points array
int lPolyVertIndex = mesh->GetPolygonVertex(lPolyIndex, lVertIndex);
//the UV index depends on the reference mode
int lUVIndex = lUseIndex ? lUVElement->GetIndexArray().GetAt(lPolyVertIndex) : lPolyVertIndex;
lUVValue = lUVElement->GetDirectArray().GetAt(lUVIndex);
_floatVec->push_back((float)lUVValue.mData[0]);
_floatVec->push_back((float)lUVValue.mData[1]);
}
}
}
else if (lUVElement->GetMappingMode() == FbxGeometryElement::eByPolygonVertex)
{
int lPolyIndexCounter = 0;
for (int lPolyIndex = 0; lPolyIndex < lPolyCount; ++lPolyIndex)
{
// build the max index array that we need to pass into MakePoly
const int lPolySize = mesh->GetPolygonSize(lPolyIndex);
for (int lVertIndex = 0; lVertIndex < lPolySize; ++lVertIndex)
{
if (lPolyIndexCounter < lIndexCount)
{
//the UV index depends on the reference mode
int lUVIndex = lUseIndex ? lUVElement->GetIndexArray().GetAt(lPolyIndexCounter) : lPolyIndexCounter;
lUVValue = lUVElement->GetDirectArray().GetAt(lUVIndex);
_floatVec->push_back((float)lUVValue.mData[0]);
_floatVec->push_back((float)lUVValue.mData[1]);
lPolyIndexCounter++;
}
}
}
}
//and now normals
if (lNormalElement->GetMappingMode() == FbxGeometryElement::eByControlPoint)
{
//Let's get normals of each vertex, since the mapping mode of normal element is by control point
for (int lVertexIndex = 0; lVertexIndex < mesh->GetControlPointsCount(); lVertexIndex++)
{
int test = mesh->GetControlPointsCount();
int lNormalIndex = 0;
//reference mode is direct, the normal index is same as vertex index.
//get normals by the index of control vertex
if (lNormalElement->GetReferenceMode() == FbxGeometryElement::eDirect)
lNormalIndex = lVertexIndex;
//reference mode is index-to-direct, get normals by the index-to-direct
if (lNormalElement->GetReferenceMode() == FbxGeometryElement::eIndexToDirect)
lNormalIndex = lNormalElement->GetIndexArray().GetAt(lVertexIndex);
//Got normals of each vertex.
FbxVector4 lNormal = lNormalElement->GetDirectArray().GetAt(lNormalIndex);
_floatVec->push_back((float)lNormal[0]);
_floatVec->push_back((float)lNormal[1]);
_floatVec->push_back((float)lNormal[2]);
}
}
else if (lNormalElement->GetMappingMode() == FbxGeometryElement::eByPolygonVertex)
{
//etc... code wont go here
}
}
}
}
So how can I know which vertexes will have which normals?
I'm trying to fill my own struct with data retrieved from a CGAL::Surface_mesh.
You can add a face to a surface mesh via..
CGAL::SM_Face_index face = SM_Surface_Mesh.add_face(SM_Vertex_Index, SM_Vertex_Index, SM_Vertex_Index);
.. but how does one retrieve that face given the SM_Face_Index? I've tried sifting through the documentation but to no avail.
InteropMesh * outputMesh = new InteropMesh();
uint32_t num = mesh1.number_of_vertices();
outputMesh->vertexCount = num;
outputMesh->vertices = new InteropVector3[num];
for (Mesh::Vertex_index vd : mesh1.vertices())
{
uint32_t index = vd; //via size_t
Point data = mesh1.point(vd);
outputMesh->vertices[index].x = (float)data.x();
outputMesh->vertices[index].y = (float)data.y();
outputMesh->vertices[index].z = (float)data.z();
}
outputMesh->indices = new uint32_t[mesh1.number_of_faces() * 3];
for (CGAL::SM_Face_index fd : mesh1.faces())
{
//? How do I get the three vertex indices?
}
A simple way of getting vertex and face indices can be done like this;
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef CGAL::Surface_mesh<K::Point_3> Mesh;
Mesh sm;
// sm created here or as a result of some CGAL function
std::vector<float> verts;
std::vector<uint32_t> indices;
//Get vertices ...
for (Mesh::Vertex_index vi : sm.vertices()) {
K::Point_3 pt = sm.point(vi);
verts.push_back((float)pt.x());
verts.push_back((float)pt.y());
verts.push_back((float)pt.z());
}
//Get face indices ...
for (Mesh::Face_index face_index : sm.faces()) {
CGAL::Vertex_around_face_circulator<Mesh> vcirc(sm.halfedge(face_index), sm), done(vcirc);
do indices.push_back(*vcirc++); while (vcirc != done);
}
This example assumes triangle output (i.e. every 3 indices describes a triangle) although faces could have more indices as Andry points out.
Another function should be added to check the face index count and split faces into triangles if there are more than 3 indices.
The Surface_mesh data structure can represent more than only triangle meshes. Meaning that you might have more than 3 vertices per face.
Once you get a face, you can navigate on its boundary edges and get the source and target vertices.
For example you can do:
Surface_mesh::Halfedge_index hf = sm.halfedge(fi);
for(Surface_mesh::Halfedge_index hi : halfedges_around_face(hf, sm))
{
Surface_mesh::Vertex_index vi = target(hi, sm);
}
You can also do it by hand:
Surface_mesh::Halfedge_index hstart = sm.halfedge(fi), hi=hstart;
do{
Surface_mesh::Vertex_index vi = target(hi, sm);
hi=sm.next(hi);
}
while(hi!=hstart)