mirror of
https://github.com/godotengine/godot.git
synced 2024-12-15 10:12:40 +08:00
b5334d14f7
Happy new year to the wonderful Godot community!
2020 has been a tough year for most of us personally, but a good year for
Godot development nonetheless with a huge amount of work done towards Godot
4.0 and great improvements backported to the long-lived 3.2 branch.
We've had close to 400 contributors to engine code this year, authoring near
7,000 commit! (And that's only for the `master` branch and for the engine code,
there's a lot more when counting docs, demos and other first-party repos.)
Here's to a great year 2021 for all Godot users 🎆
486 lines
20 KiB
C++
486 lines
20 KiB
C++
/*************************************************************************/
|
|
/* FBXMeshGeometry.cpp */
|
|
/*************************************************************************/
|
|
/* This file is part of: */
|
|
/* GODOT ENGINE */
|
|
/* https://godotengine.org */
|
|
/*************************************************************************/
|
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
|
/* */
|
|
/* 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. */
|
|
/*************************************************************************/
|
|
|
|
/*
|
|
Open Asset Import Library (assimp)
|
|
----------------------------------------------------------------------
|
|
|
|
Copyright (c) 2006-2019, assimp team
|
|
|
|
|
|
All rights reserved.
|
|
|
|
Redistribution and use of this software in source and binary forms,
|
|
with or without modification, are permitted provided that the
|
|
following conditions are met:
|
|
|
|
* Redistributions of source code must retain the above
|
|
copyright notice, this list of conditions and the
|
|
following disclaimer.
|
|
|
|
* Redistributions in binary form must reproduce the above
|
|
copyright notice, this list of conditions and the
|
|
following disclaimer in the documentation and/or other
|
|
materials provided with the distribution.
|
|
|
|
* Neither the name of the assimp team, nor the names of its
|
|
contributors may be used to endorse or promote products
|
|
derived from this software without specific prior
|
|
written permission of the assimp team.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
----------------------------------------------------------------------
|
|
*/
|
|
|
|
/** @file FBXMeshGeometry.cpp
|
|
* @brief Assimp::FBX::MeshGeometry implementation
|
|
*/
|
|
|
|
#include <functional>
|
|
|
|
#include "FBXDocument.h"
|
|
#include "FBXDocumentUtil.h"
|
|
#include "FBXImportSettings.h"
|
|
#include "FBXMeshGeometry.h"
|
|
#include "core/math/vector3.h"
|
|
|
|
namespace FBXDocParser {
|
|
|
|
using namespace Util;
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
Geometry::Geometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) :
|
|
Object(id, element, name), skin() {
|
|
const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "Deformer");
|
|
for (const Connection *con : conns) {
|
|
const Skin *sk = ProcessSimpleConnection<Skin>(*con, false, "Skin -> Geometry", element);
|
|
if (sk) {
|
|
skin = sk;
|
|
}
|
|
const BlendShape *bsp = ProcessSimpleConnection<BlendShape>(*con, false, "BlendShape -> Geometry",
|
|
element);
|
|
if (bsp) {
|
|
blendShapes.push_back(bsp);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
Geometry::~Geometry() {
|
|
// empty
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
const std::vector<const BlendShape *> &Geometry::get_blend_shapes() const {
|
|
return blendShapes;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
const Skin *Geometry::DeformerSkin() const {
|
|
return skin;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
MeshGeometry::MeshGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) :
|
|
Geometry(id, element, name, doc) {
|
|
print_verbose("mesh name: " + String(name.c_str()));
|
|
|
|
ScopePtr sc = element->Compound();
|
|
ERR_FAIL_COND_MSG(sc == nullptr, "failed to read geometry, prevented crash");
|
|
ERR_FAIL_COND_MSG(!HasElement(sc, "Vertices"), "Detected mesh with no vertexes, didn't populate the mesh");
|
|
|
|
// must have Mesh elements:
|
|
const ElementPtr Vertices = GetRequiredElement(sc, "Vertices", element);
|
|
const ElementPtr PolygonVertexIndex = GetRequiredElement(sc, "PolygonVertexIndex", element);
|
|
|
|
if (HasElement(sc, "Edges")) {
|
|
const ElementPtr element_edges = GetRequiredElement(sc, "Edges", element);
|
|
ParseVectorDataArray(m_edges, element_edges);
|
|
}
|
|
|
|
// read mesh data into arrays
|
|
ParseVectorDataArray(m_vertices, Vertices);
|
|
ParseVectorDataArray(m_face_indices, PolygonVertexIndex);
|
|
|
|
ERR_FAIL_COND_MSG(m_vertices.empty(), "mesh with no vertexes in FBX file, did you mean to delete it?");
|
|
ERR_FAIL_COND_MSG(m_face_indices.empty(), "mesh has no faces, was this intended?");
|
|
|
|
// Retrieve layer elements, for all of the mesh
|
|
const ElementCollection &Layer = sc->GetCollection("Layer");
|
|
|
|
// Store all layers
|
|
std::vector<std::tuple<int, std::string>> valid_layers;
|
|
|
|
// now read the sub mesh information from the geometry (normals, uvs, etc)
|
|
for (ElementMap::const_iterator it = Layer.first; it != Layer.second; ++it) {
|
|
const ScopePtr layer = GetRequiredScope(it->second);
|
|
const ElementCollection &LayerElement = layer->GetCollection("LayerElement");
|
|
for (ElementMap::const_iterator eit = LayerElement.first; eit != LayerElement.second; ++eit) {
|
|
std::string layer_name = eit->first;
|
|
ElementPtr element_layer = eit->second;
|
|
const ScopePtr layer_element = GetRequiredScope(element_layer);
|
|
|
|
// Actual usable 'type' LayerElementUV, LayerElementNormal, etc
|
|
const ElementPtr Type = GetRequiredElement(layer_element, "Type");
|
|
const ElementPtr TypedIndex = GetRequiredElement(layer_element, "TypedIndex");
|
|
const std::string &type = ParseTokenAsString(GetRequiredToken(Type, 0));
|
|
const int typedIndex = ParseTokenAsInt(GetRequiredToken(TypedIndex, 0));
|
|
|
|
// we only need the layer name and the typed index.
|
|
valid_layers.push_back(std::tuple<int, std::string>(typedIndex, type));
|
|
}
|
|
}
|
|
|
|
// get object / mesh directly from the FBX by the element ID.
|
|
const ScopePtr top = GetRequiredScope(element);
|
|
|
|
// iterate over all layers for the mesh (uvs, normals, smoothing groups, colors, etc)
|
|
for (size_t x = 0; x < valid_layers.size(); x++) {
|
|
const int layer_id = std::get<0>(valid_layers[x]);
|
|
const std::string &layer_type_name = std::get<1>(valid_layers[x]);
|
|
|
|
// Get collection of elements from the XLayerMap (example: LayerElementUV)
|
|
// this must contain our proper elements.
|
|
|
|
// This is stupid, because it means we select them ALL not just the one we want.
|
|
// but it's fine we can match by id.
|
|
|
|
const ElementCollection &candidates = top->GetCollection(layer_type_name);
|
|
|
|
ElementMap::const_iterator iter;
|
|
for (iter = candidates.first; iter != candidates.second; ++iter) {
|
|
const ScopePtr layer_scope = GetRequiredScope(iter->second);
|
|
TokenPtr layer_token = GetRequiredToken(iter->second, 0);
|
|
const int index = ParseTokenAsInt(layer_token);
|
|
|
|
ERR_FAIL_COND_MSG(layer_scope == nullptr, "prevented crash, layer scope is invalid");
|
|
|
|
if (index == layer_id) {
|
|
const std::string &MappingInformationType = ParseTokenAsString(GetRequiredToken(
|
|
GetRequiredElement(layer_scope, "MappingInformationType"), 0));
|
|
|
|
const std::string &ReferenceInformationType = ParseTokenAsString(GetRequiredToken(
|
|
GetRequiredElement(layer_scope, "ReferenceInformationType"), 0));
|
|
|
|
if (layer_type_name == "LayerElementUV") {
|
|
if (index == 0) {
|
|
m_uv_0 = resolve_vertex_data_array<Vector2>(layer_scope, MappingInformationType, ReferenceInformationType, "UV");
|
|
} else if (index == 1) {
|
|
m_uv_1 = resolve_vertex_data_array<Vector2>(layer_scope, MappingInformationType, ReferenceInformationType, "UV");
|
|
}
|
|
} else if (layer_type_name == "LayerElementMaterial") {
|
|
m_material_allocation_ids = resolve_vertex_data_array<int>(layer_scope, MappingInformationType, ReferenceInformationType, "Materials");
|
|
} else if (layer_type_name == "LayerElementNormal") {
|
|
m_normals = resolve_vertex_data_array<Vector3>(layer_scope, MappingInformationType, ReferenceInformationType, "Normals");
|
|
} else if (layer_type_name == "LayerElementColor") {
|
|
m_colors = resolve_vertex_data_array<Color>(layer_scope, MappingInformationType, ReferenceInformationType, "Colors", "ColorIndex");
|
|
// NOTE: this is a useful sanity check to ensure you're getting any color data which is not default.
|
|
// const Color first_color_check = m_colors.data[0];
|
|
// bool colors_are_all_the_same = true;
|
|
// size_t i = 1;
|
|
// for(i = 1; i < m_colors.data.size(); i++)
|
|
// {
|
|
// const Color current_color = m_colors.data[i];
|
|
// if(current_color.is_equal_approx(first_color_check))
|
|
// {
|
|
// continue;
|
|
// }
|
|
// else
|
|
// {
|
|
// colors_are_all_the_same = false;
|
|
// break;
|
|
// }
|
|
// }
|
|
//
|
|
// if(colors_are_all_the_same)
|
|
// {
|
|
// print_error("Color serialisation is not working for vertex colors some should be different in the test asset.");
|
|
// }
|
|
// else
|
|
// {
|
|
// print_verbose("Color array has unique colors at index: " + itos(i));
|
|
// }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
print_verbose("Mesh statistics \nuv_0: " + m_uv_0.debug_info() + "\nuv_1: " + m_uv_1.debug_info() + "\nvertices: " + itos(m_vertices.size()));
|
|
|
|
// Compose the edge of the mesh.
|
|
// You can see how the edges are stored into the FBX here: https://gist.github.com/AndreaCatania/da81840f5aa3b2feedf189e26c5a87e6
|
|
for (size_t i = 0; i < m_edges.size(); i += 1) {
|
|
ERR_FAIL_INDEX_MSG((size_t)m_edges[i], m_face_indices.size(), "The edge is pointing to a weird location in the face indices. The FBX is corrupted.");
|
|
int polygon_vertex_0 = m_face_indices[m_edges[i]];
|
|
int polygon_vertex_1;
|
|
if (polygon_vertex_0 < 0) {
|
|
// The polygon_vertex_0 points to the end of a polygon, so it's
|
|
// connected with the beginning of polygon in the edge list.
|
|
|
|
// Fist invert the vertex.
|
|
polygon_vertex_0 = ~polygon_vertex_0;
|
|
|
|
// Search the start vertex of the polygon.
|
|
// Iterate from the polygon_vertex_index backward till the start of
|
|
// the polygon is found.
|
|
ERR_FAIL_COND_MSG(m_edges[i] - 1 < 0, "The polygon is not yet started and we already need the final vertex. This FBX is corrupted.");
|
|
bool found_it = false;
|
|
for (int x = m_edges[i] - 1; x >= 0; x -= 1) {
|
|
if (x == 0) {
|
|
// This for sure is the start.
|
|
polygon_vertex_1 = m_face_indices[x];
|
|
found_it = true;
|
|
break;
|
|
} else if (m_face_indices[x] < 0) {
|
|
// This is the end of the previous polygon, so the next is
|
|
// the start of the polygon we need.
|
|
polygon_vertex_1 = m_face_indices[x + 1];
|
|
found_it = true;
|
|
break;
|
|
}
|
|
}
|
|
// As the algorithm above, this check is useless. Because the first
|
|
// ever vertex is always considered the begining of a polygon.
|
|
ERR_FAIL_COND_MSG(found_it == false, "Was not possible to find the first vertex of this polygon. FBX file is corrupted.");
|
|
|
|
} else {
|
|
ERR_FAIL_INDEX_MSG((size_t)(m_edges[i] + 1), m_face_indices.size(), "FBX The other FBX edge seems to point to an invalid vertices. This FBX file is corrupted.");
|
|
// Take the next vertex
|
|
polygon_vertex_1 = m_face_indices[m_edges[i] + 1];
|
|
}
|
|
|
|
if (polygon_vertex_1 < 0) {
|
|
// We don't care if the `polygon_vertex_1` is the end of the polygon,
|
|
// for `polygon_vertex_1` so we can just invert it.
|
|
polygon_vertex_1 = ~polygon_vertex_1;
|
|
}
|
|
|
|
ERR_FAIL_COND_MSG(polygon_vertex_0 == polygon_vertex_1, "The vertices of this edge can't be the same, Is this a point???. This FBX file is corrupted.");
|
|
|
|
// Just create the edge.
|
|
edge_map.push_back({ polygon_vertex_0, polygon_vertex_1 });
|
|
}
|
|
}
|
|
|
|
MeshGeometry::~MeshGeometry() {
|
|
// empty
|
|
}
|
|
|
|
const std::vector<Vector3> &MeshGeometry::get_vertices() const {
|
|
return m_vertices;
|
|
}
|
|
|
|
const std::vector<MeshGeometry::Edge> &MeshGeometry::get_edge_map() const {
|
|
return edge_map;
|
|
}
|
|
|
|
const std::vector<int> &MeshGeometry::get_polygon_indices() const {
|
|
return m_face_indices;
|
|
}
|
|
|
|
const std::vector<int> &MeshGeometry::get_edges() const {
|
|
return m_edges;
|
|
}
|
|
|
|
const MeshGeometry::MappingData<Vector3> &MeshGeometry::get_normals() const {
|
|
return m_normals;
|
|
}
|
|
|
|
const MeshGeometry::MappingData<Vector2> &MeshGeometry::get_uv_0() const {
|
|
//print_verbose("get uv_0 " + m_uv_0.debug_info() );
|
|
return m_uv_0;
|
|
}
|
|
|
|
const MeshGeometry::MappingData<Vector2> &MeshGeometry::get_uv_1() const {
|
|
//print_verbose("get uv_1 " + m_uv_1.debug_info() );
|
|
return m_uv_1;
|
|
}
|
|
|
|
const MeshGeometry::MappingData<Color> &MeshGeometry::get_colors() const {
|
|
return m_colors;
|
|
}
|
|
|
|
const MeshGeometry::MappingData<int> &MeshGeometry::get_material_allocation_id() const {
|
|
return m_material_allocation_ids;
|
|
}
|
|
|
|
int MeshGeometry::get_edge_id(const std::vector<Edge> &p_map, int p_vertex_a, int p_vertex_b) {
|
|
for (size_t i = 0; i < p_map.size(); i += 1) {
|
|
if ((p_map[i].vertex_0 == p_vertex_a && p_map[i].vertex_1 == p_vertex_b) || (p_map[i].vertex_1 == p_vertex_a && p_map[i].vertex_0 == p_vertex_b)) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
MeshGeometry::Edge MeshGeometry::get_edge(const std::vector<Edge> &p_map, int p_id) {
|
|
ERR_FAIL_INDEX_V_MSG((size_t)p_id, p_map.size(), Edge({ -1, -1 }), "ID not found.");
|
|
return p_map[p_id];
|
|
}
|
|
|
|
template <class T>
|
|
MeshGeometry::MappingData<T> MeshGeometry::resolve_vertex_data_array(
|
|
const ScopePtr source,
|
|
const std::string &MappingInformationType,
|
|
const std::string &ReferenceInformationType,
|
|
const std::string &dataElementName,
|
|
const std::string &indexOverride) {
|
|
ERR_FAIL_COND_V_MSG(source == nullptr, MappingData<T>(), "Invalid scope operator preventing memory corruption");
|
|
|
|
// UVIndex, MaterialIndex, NormalIndex, etc..
|
|
std::string indexDataElementName;
|
|
|
|
if (indexOverride != "") {
|
|
// Colors should become ColorIndex
|
|
indexDataElementName = indexOverride;
|
|
} else {
|
|
// Some indexes will exist.
|
|
indexDataElementName = dataElementName + "Index";
|
|
}
|
|
|
|
// goal: expand everything to be per vertex
|
|
|
|
ReferenceType l_ref_type = ReferenceType::direct;
|
|
|
|
// Read the reference type into the enumeration
|
|
if (ReferenceInformationType == "IndexToDirect") {
|
|
l_ref_type = ReferenceType::index_to_direct;
|
|
} else if (ReferenceInformationType == "Index") {
|
|
// set non legacy index to direct mapping
|
|
l_ref_type = ReferenceType::index;
|
|
} else if (ReferenceInformationType == "Direct") {
|
|
l_ref_type = ReferenceType::direct;
|
|
} else {
|
|
ERR_FAIL_V_MSG(MappingData<T>(), "invalid reference type has the FBX format changed?");
|
|
}
|
|
|
|
MapType l_map_type = MapType::none;
|
|
|
|
if (MappingInformationType == "None") {
|
|
l_map_type = MapType::none;
|
|
} else if (MappingInformationType == "ByVertice") {
|
|
l_map_type = MapType::vertex;
|
|
} else if (MappingInformationType == "ByPolygonVertex") {
|
|
l_map_type = MapType::polygon_vertex;
|
|
} else if (MappingInformationType == "ByPolygon") {
|
|
l_map_type = MapType::polygon;
|
|
} else if (MappingInformationType == "ByEdge") {
|
|
l_map_type = MapType::edge;
|
|
} else if (MappingInformationType == "AllSame") {
|
|
l_map_type = MapType::all_the_same;
|
|
} else {
|
|
print_error("invalid mapping type: " + String(MappingInformationType.c_str()));
|
|
}
|
|
|
|
// create mapping data
|
|
MeshGeometry::MappingData<T> tempData;
|
|
tempData.map_type = l_map_type;
|
|
tempData.ref_type = l_ref_type;
|
|
|
|
// parse data into array
|
|
ParseVectorDataArray(tempData.data, GetRequiredElement(source, dataElementName));
|
|
|
|
// index array wont always exist
|
|
const ElementPtr element = GetOptionalElement(source, indexDataElementName);
|
|
if (element) {
|
|
ParseVectorDataArray(tempData.index, element);
|
|
}
|
|
|
|
return tempData;
|
|
}
|
|
// ------------------------------------------------------------------------------------------------
|
|
ShapeGeometry::ShapeGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) :
|
|
Geometry(id, element, name, doc) {
|
|
const ScopePtr sc = element->Compound();
|
|
if (nullptr == sc) {
|
|
DOMError("failed to read Geometry object (class: Shape), no data scope found");
|
|
}
|
|
const ElementPtr Indexes = GetRequiredElement(sc, "Indexes", element);
|
|
const ElementPtr Normals = GetRequiredElement(sc, "Normals", element);
|
|
const ElementPtr Vertices = GetRequiredElement(sc, "Vertices", element);
|
|
ParseVectorDataArray(m_indices, Indexes);
|
|
ParseVectorDataArray(m_vertices, Vertices);
|
|
ParseVectorDataArray(m_normals, Normals);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
ShapeGeometry::~ShapeGeometry() {
|
|
// empty
|
|
}
|
|
// ------------------------------------------------------------------------------------------------
|
|
const std::vector<Vector3> &ShapeGeometry::GetVertices() const {
|
|
return m_vertices;
|
|
}
|
|
// ------------------------------------------------------------------------------------------------
|
|
const std::vector<Vector3> &ShapeGeometry::GetNormals() const {
|
|
return m_normals;
|
|
}
|
|
// ------------------------------------------------------------------------------------------------
|
|
const std::vector<unsigned int> &ShapeGeometry::GetIndices() const {
|
|
return m_indices;
|
|
}
|
|
// ------------------------------------------------------------------------------------------------
|
|
LineGeometry::LineGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) :
|
|
Geometry(id, element, name, doc) {
|
|
const ScopePtr sc = element->Compound();
|
|
if (!sc) {
|
|
DOMError("failed to read Geometry object (class: Line), no data scope found");
|
|
}
|
|
const ElementPtr Points = GetRequiredElement(sc, "Points", element);
|
|
const ElementPtr PointsIndex = GetRequiredElement(sc, "PointsIndex", element);
|
|
ParseVectorDataArray(m_vertices, Points);
|
|
ParseVectorDataArray(m_indices, PointsIndex);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
LineGeometry::~LineGeometry() {
|
|
// empty
|
|
}
|
|
// ------------------------------------------------------------------------------------------------
|
|
const std::vector<Vector3> &LineGeometry::GetVertices() const {
|
|
return m_vertices;
|
|
}
|
|
// ------------------------------------------------------------------------------------------------
|
|
const std::vector<int> &LineGeometry::GetIndices() const {
|
|
return m_indices;
|
|
}
|
|
} // namespace FBXDocParser
|