This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/collada/MeshImporter.cpp

1171 lines
34 KiB
C++
Raw Normal View History

/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Contributor(s): Chingiz Dyussenov, Arystanbek Dyussenov, Nathan Letwory.
*
* ***** END GPL LICENSE BLOCK *****
*/
2011-02-27 20:30:35 +00:00
/** \file blender/collada/MeshImporter.cpp
* \ingroup collada
*/
#include <algorithm>
#if !defined(WIN32)
#include <iostream>
#endif
/* COLLADABU_ASSERT, may be able to remove later */
#include "COLLADABUPlatform.h"
#include "COLLADAFWMeshPrimitive.h"
#include "COLLADAFWMeshVertexData.h"
#include "COLLADAFWPolygons.h"
extern "C" {
#include "BKE_customdata.h"
#include "BKE_displist.h"
#include "BKE_global.h"
#include "BKE_library.h"
#include "BKE_material.h"
#include "BKE_mesh.h"
#include "BKE_object.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_string.h"
#include "BLI_edgehash.h"
#include "MEM_guardedalloc.h"
}
#include "ArmatureImporter.h"
#include "MeshImporter.h"
#include "collada_utils.h"
// get node name, or fall back to original id if not present (name is optional)
template<class T>
static const std::string bc_get_dae_name(T *node)
{
return node->getName().size() ? node->getName(): node->getOriginalId();
}
static const char *bc_primTypeToStr(COLLADAFW::MeshPrimitive::PrimitiveType type)
{
switch (type) {
2012-06-12 22:05:33 +00:00
case COLLADAFW::MeshPrimitive::LINES:
return "LINES";
case COLLADAFW::MeshPrimitive::LINE_STRIPS:
return "LINESTRIPS";
case COLLADAFW::MeshPrimitive::POLYGONS:
return "POLYGONS";
case COLLADAFW::MeshPrimitive::POLYLIST:
return "POLYLIST";
case COLLADAFW::MeshPrimitive::TRIANGLES:
return "TRIANGLES";
case COLLADAFW::MeshPrimitive::TRIANGLE_FANS:
return "TRIANGLE_FANS";
case COLLADAFW::MeshPrimitive::TRIANGLE_STRIPS:
return "TRIANGLE_STRIPS";
2012-06-12 22:05:33 +00:00
case COLLADAFW::MeshPrimitive::POINTS:
return "POINTS";
case COLLADAFW::MeshPrimitive::UNDEFINED_PRIMITIVE_TYPE:
return "UNDEFINED_PRIMITIVE_TYPE";
}
return "UNKNOWN";
}
static const char *bc_geomTypeToStr(COLLADAFW::Geometry::GeometryType type)
{
switch (type) {
2012-06-12 22:05:33 +00:00
case COLLADAFW::Geometry::GEO_TYPE_MESH:
return "MESH";
case COLLADAFW::Geometry::GEO_TYPE_SPLINE:
return "SPLINE";
case COLLADAFW::Geometry::GEO_TYPE_CONVEX_MESH:
return "CONVEX_MESH";
case COLLADAFW::Geometry::GEO_TYPE_UNKNOWN:
default:
return "UNKNOWN";
}
}
UVDataWrapper::UVDataWrapper(COLLADAFW::MeshVertexData& vdata) : mVData(&vdata)
2012-06-12 22:05:33 +00:00
{
}
#ifdef COLLADA_DEBUG
void WVDataWrapper::print()
{
fprintf(stderr, "UVs:\n");
switch (mVData->getType()) {
2012-06-12 22:05:33 +00:00
case COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT:
{
2012-06-12 22:05:33 +00:00
COLLADAFW::ArrayPrimitiveType<float> *values = mVData->getFloatValues();
if (values->getCount()) {
for (int i = 0; i < values->getCount(); i += 2) {
2012-06-12 22:05:33 +00:00
fprintf(stderr, "%.1f, %.1f\n", (*values)[i], (*values)[i + 1]);
}
}
}
break;
2012-06-12 22:05:33 +00:00
case COLLADAFW::MeshVertexData::DATA_TYPE_DOUBLE:
{
2012-06-12 22:05:33 +00:00
COLLADAFW::ArrayPrimitiveType<double> *values = mVData->getDoubleValues();
if (values->getCount()) {
for (int i = 0; i < values->getCount(); i += 2) {
2012-06-12 22:05:33 +00:00
fprintf(stderr, "%.1f, %.1f\n", (float)(*values)[i], (float)(*values)[i + 1]);
}
}
}
break;
}
fprintf(stderr, "\n");
}
#endif
void UVDataWrapper::getUV(int uv_index, float *uv)
{
int stride = mVData->getStride(0);
2012-06-12 22:05:33 +00:00
if (stride == 0) stride = 2;
switch (mVData->getType()) {
2012-06-12 22:05:33 +00:00
case COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT:
{
2012-06-12 22:05:33 +00:00
COLLADAFW::ArrayPrimitiveType<float> *values = mVData->getFloatValues();
if (values->empty()) return;
2012-06-12 22:05:33 +00:00
uv[0] = (*values)[uv_index * stride];
uv[1] = (*values)[uv_index * stride + 1];
}
break;
2012-06-12 22:05:33 +00:00
case COLLADAFW::MeshVertexData::DATA_TYPE_DOUBLE:
{
2012-06-12 22:05:33 +00:00
COLLADAFW::ArrayPrimitiveType<double> *values = mVData->getDoubleValues();
if (values->empty()) return;
2012-06-12 22:05:33 +00:00
uv[0] = (float)(*values)[uv_index * stride];
uv[1] = (float)(*values)[uv_index * stride + 1];
}
break;
2012-06-12 22:05:33 +00:00
case COLLADAFW::MeshVertexData::DATA_TYPE_UNKNOWN:
default:
fprintf(stderr, "MeshImporter.getUV(): unknown data type\n");
}
}
VCOLDataWrapper::VCOLDataWrapper(COLLADAFW::MeshVertexData& vdata) : mVData(&vdata) {}
void VCOLDataWrapper::get_vcol(int v_index, MLoopCol *mloopcol)
{
int stride = mVData->getStride(0);
2014-05-08 04:53:05 +10:00
if (stride == 0) stride = 3;
switch (mVData->getType()) {
case COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT:
{
COLLADAFW::ArrayPrimitiveType<float> *values = mVData->getFloatValues();
if (values->empty() || values->getCount() <= (v_index * stride + 2)) return; // xxx need to create an error instead
mloopcol->r = unit_float_to_uchar_clamp((*values)[v_index * stride]);
mloopcol->g = unit_float_to_uchar_clamp((*values)[v_index * stride + 1]);
mloopcol->b = unit_float_to_uchar_clamp((*values)[v_index * stride + 2]);
}
break;
case COLLADAFW::MeshVertexData::DATA_TYPE_DOUBLE:
{
COLLADAFW::ArrayPrimitiveType<double> *values = mVData->getDoubleValues();
if (values->empty() || values->getCount() <= (v_index * stride + 2)) return; // xxx need to create an error instead
mloopcol->r = unit_float_to_uchar_clamp((*values)[v_index * stride]);
mloopcol->g = unit_float_to_uchar_clamp((*values)[v_index * stride + 1]);
mloopcol->b = unit_float_to_uchar_clamp((*values)[v_index * stride + 2]);
}
break;
default:
fprintf(stderr, "VCOLDataWrapper.getvcol(): unknown data type\n");
}
}
MeshImporter::MeshImporter(UnitConverter *unitconv, ArmatureImporter *arm, Main *bmain, Scene *sce, ViewLayer *view_layer):
2018-06-08 08:10:35 +02:00
unitconverter(unitconv),
m_bmain(bmain),
scene(sce),
view_layer(view_layer),
armature_importer(arm) {
}
bool MeshImporter::set_poly_indices(MPoly *mpoly, MLoop *mloop, int loop_index, unsigned int *indices, int loop_count)
{
mpoly->loopstart = loop_index;
mpoly->totloop = loop_count;
bool broken_loop = false;
for (int index=0; index < loop_count; index++) {
/* Test if loop defines a hole */
if (!broken_loop) {
for (int i = 0; i < index; i++) {
if (indices[i] == indices[index]) {
// duplicate index -> not good
broken_loop = true;
}
}
}
mloop->v = indices[index];
mloop++;
}
return broken_loop;
}
void MeshImporter::set_vcol(MLoopCol *mlc, VCOLDataWrapper &vob, int loop_index, COLLADAFW::IndexList &index_list, int count)
{
int index;
2015-01-24 01:59:09 +11:00
for (index = 0; index < count; index++, mlc++) {
int v_index = index_list.getIndex(index + loop_index);
vob.get_vcol(v_index,mlc);
}
}
void MeshImporter::set_face_uv(MLoopUV *mloopuv, UVDataWrapper &uvs,
int start_index, COLLADAFW::IndexList& index_list, int count)
{
// per face vertex indices, this means for quad we have 4 indices, not 8
COLLADAFW::UIntValuesArray& indices = index_list.getIndices();
for (int index = 0; index < count; index++) {
int uv_index = indices[index+start_index];
uvs.getUV(uv_index, mloopuv[index].uv);
}
}
#ifdef COLLADA_DEBUG
void MeshImporter::print_index_list(COLLADAFW::IndexList& index_list)
{
fprintf(stderr, "Index list for \"%s\":\n", index_list.getName().c_str());
for (int i = 0; i < index_list.getIndicesCount(); i += 2) {
fprintf(stderr, "%u, %u\n", index_list.getIndex(i), index_list.getIndex(i + 1));
}
fprintf(stderr, "\n");
}
#endif
2012-06-12 22:05:33 +00:00
bool MeshImporter::is_nice_mesh(COLLADAFW::Mesh *mesh) // checks if mesh has supported primitive types: lines, polylist, triangles, triangle_fans
{
COLLADAFW::MeshPrimitiveArray& prim_arr = mesh->getMeshPrimitives();
const std::string &name = bc_get_dae_name(mesh);
for (unsigned i = 0; i < prim_arr.getCount(); i++) {
COLLADAFW::MeshPrimitive *mp = prim_arr[i];
COLLADAFW::MeshPrimitive::PrimitiveType type = mp->getPrimitiveType();
const char *type_str = bc_primTypeToStr(type);
// OpenCollada passes POLYGONS type for <polylist>
if (type == COLLADAFW::MeshPrimitive::POLYLIST || type == COLLADAFW::MeshPrimitive::POLYGONS) {
2012-06-12 22:05:33 +00:00
COLLADAFW::Polygons *mpvc = (COLLADAFW::Polygons *)mp;
COLLADAFW::Polygons::VertexCountArray& vca = mpvc->getGroupedVerticesVertexCountArray();
int hole_count = 0;
int nonface_count = 0;
2012-03-28 05:03:24 +00:00
for (unsigned int j = 0; j < vca.getCount(); j++) {
int count = vca[j];
if (abs(count) < 3) {
nonface_count++;
}
if (count < 0) {
hole_count ++;
}
}
if (hole_count > 0) {
fprintf(stderr, "WARNING: Primitive %s in %s: %d holes not imported (unsupported)\n", type_str, name.c_str(), hole_count);
}
if (nonface_count > 0) {
fprintf(stderr, "WARNING: Primitive %s in %s: %d faces with vertex count < 3 (rejected)\n", type_str, name.c_str(), nonface_count);
}
}
2012-06-27 18:29:47 +00:00
else if (type == COLLADAFW::MeshPrimitive::LINES) {
// TODO: Add Checker for line syntax here
}
2012-06-12 22:05:33 +00:00
else if (type != COLLADAFW::MeshPrimitive::TRIANGLES && type != COLLADAFW::MeshPrimitive::TRIANGLE_FANS) {
fprintf(stderr, "ERROR: Primitive type %s is not supported.\n", type_str);
return false;
}
}
return true;
}
void MeshImporter::read_vertices(COLLADAFW::Mesh *mesh, Mesh *me)
{
// vertices
COLLADAFW::MeshVertexData& pos = mesh->getPositions();
if (pos.empty()) {
return;
}
int stride = pos.getStride(0);
2012-06-12 22:05:33 +00:00
if (stride == 0) stride = 3;
me->totvert = pos.getFloatValues()->getCount() / stride;
me->mvert = (MVert *)CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, NULL, me->totvert);
MVert *mvert;
int i;
for (i = 0, mvert = me->mvert; i < me->totvert; i++, mvert++) {
get_vector(mvert->co, pos, i, stride);
}
}
// =====================================================================
// condition 1: The Primitive has normals
// condition 2: The number of normals equals the number of faces.
// return true if both conditions apply.
// return false otherwise.
// =====================================================================
bool MeshImporter::primitive_has_useable_normals(COLLADAFW::MeshPrimitive *mp) {
bool has_useable_normals = false;
int normals_count = mp->getNormalIndices().getCount();
if (normals_count > 0) {
int index_count = mp->getPositionIndices().getCount();
if (index_count == normals_count)
has_useable_normals = true;
else {
fprintf(stderr,
2012-06-12 22:05:33 +00:00
"Warning: Number of normals %d is different from the number of vertices %d, skipping normals\n",
normals_count, index_count);
}
}
return has_useable_normals;
}
// =====================================================================
// Assume that only TRIANGLES, TRIANGLE_FANS, POLYLIST and POLYGONS
// have faces. (to be verified)
// =====================================================================
bool MeshImporter::primitive_has_faces(COLLADAFW::MeshPrimitive *mp) {
bool has_faces = false;
int type = mp->getPrimitiveType();
switch (type) {
case COLLADAFW::MeshPrimitive::TRIANGLES:
case COLLADAFW::MeshPrimitive::TRIANGLE_FANS:
case COLLADAFW::MeshPrimitive::POLYLIST:
2013-07-19 10:40:43 +00:00
case COLLADAFW::MeshPrimitive::POLYGONS:
{
has_faces = true;
break;
}
default: {
has_faces = false;
break;
}
}
return has_faces;
}
static std::string extract_vcolname(const COLLADAFW::String &collada_id) {
std::string colname = collada_id;
int spos = colname.find("-mesh-colors-");
if (spos != std::string::npos) {
colname = colname.substr(spos+13);
}
return colname;
}
// =================================================================
2012-08-11 22:12:32 +00:00
// Return the number of faces by summing up
// the facecounts of the parts.
// hint: This is done because mesh->getFacesCount() does
// count loose edges as extra faces, which is not what we want here.
// =================================================================
void MeshImporter::allocate_poly_data(COLLADAFW::Mesh *collada_mesh, Mesh *me)
2012-08-11 22:12:32 +00:00
{
COLLADAFW::MeshPrimitiveArray& prim_arr = collada_mesh->getMeshPrimitives();
int total_poly_count = 0;
int total_loop_count = 0;
// collect edge_count and face_count from all parts
for (int i = 0; i < prim_arr.getCount(); i++) {
COLLADAFW::MeshPrimitive *mp = prim_arr[i];
int type = mp->getPrimitiveType();
switch (type) {
case COLLADAFW::MeshPrimitive::TRIANGLES:
case COLLADAFW::MeshPrimitive::TRIANGLE_FANS:
case COLLADAFW::MeshPrimitive::POLYLIST:
2013-07-19 10:40:43 +00:00
case COLLADAFW::MeshPrimitive::POLYGONS:
{
COLLADAFW::Polygons *mpvc = (COLLADAFW::Polygons *)mp;
size_t prim_poly_count = mpvc->getFaceCount();
size_t prim_loop_count = 0;
for (int index=0; index < prim_poly_count; index++)
{
int vcount = get_vertex_count(mpvc, index);
if (vcount > 0) {
prim_loop_count += vcount;
total_poly_count++;
}
else {
// TODO: this is a hole and not another polygon!
}
}
total_loop_count += prim_loop_count;
break;
}
2013-07-19 10:40:43 +00:00
default:
break;
}
}
// Add the data containers
if (total_poly_count > 0) {
me->totpoly = total_poly_count;
me->totloop = total_loop_count;
me->mpoly = (MPoly *)CustomData_add_layer(&me->pdata, CD_MPOLY, CD_CALLOC, NULL, me->totpoly);
me->mloop = (MLoop *)CustomData_add_layer(&me->ldata, CD_MLOOP, CD_CALLOC, NULL, me->totloop);
unsigned int totuvset = collada_mesh->getUVCoords().getInputInfosArray().getCount();
for (int i = 0; i < totuvset; i++) {
if (collada_mesh->getUVCoords().getLength(i) == 0) {
totuvset = 0;
break;
}
}
if (totuvset > 0) {
for (int i = 0; i < totuvset; i++) {
COLLADAFW::MeshVertexData::InputInfos *info = collada_mesh->getUVCoords().getInputInfosArray()[i];
COLLADAFW::String &uvname = info->mName;
// Allocate space for UV_data
CustomData_add_layer_named(&me->ldata, CD_MLOOPUV, CD_DEFAULT, NULL, me->totloop, uvname.c_str());
}
// activate the first uv map
me->mloopuv = (MLoopUV *) CustomData_get_layer_n(&me->ldata, CD_MLOOPUV, 0);
}
int totcolset = collada_mesh->getColors().getInputInfosArray().getCount();
if (totcolset > 0) {
for (int i = 0; i < totcolset; i++) {
COLLADAFW::MeshVertexData::InputInfos *info = collada_mesh->getColors().getInputInfosArray()[i];
COLLADAFW::String colname = extract_vcolname(info->mName);
CustomData_add_layer_named(&me->ldata,CD_MLOOPCOL,CD_DEFAULT,NULL,me->totloop, colname.c_str());
}
me->mloopcol = (MLoopCol *) CustomData_get_layer_n(&me->ldata, CD_MLOOPCOL, 0);
}
}
}
unsigned int MeshImporter::get_vertex_count(COLLADAFW::Polygons *mp, int index) {
int type = mp->getPrimitiveType();
int result;
switch (type) {
case COLLADAFW::MeshPrimitive::TRIANGLES:
2013-07-19 10:40:43 +00:00
case COLLADAFW::MeshPrimitive::TRIANGLE_FANS:
{
result = 3;
break;
}
case COLLADAFW::MeshPrimitive::POLYLIST:
2013-07-19 10:40:43 +00:00
case COLLADAFW::MeshPrimitive::POLYGONS:
{
result = mp->getGroupedVerticesVertexCountArray()[index];
break;
}
2013-07-19 10:40:43 +00:00
default:
{
result = -1;
break;
}
}
return result;
}
unsigned int MeshImporter::get_loose_edge_count(COLLADAFW::Mesh *mesh) {
COLLADAFW::MeshPrimitiveArray& prim_arr = mesh->getMeshPrimitives();
int loose_edge_count = 0;
// collect edge_count and face_count from all parts
for (int i = 0; i < prim_arr.getCount(); i++) {
COLLADAFW::MeshPrimitive *mp = prim_arr[i];
int type = mp->getPrimitiveType();
switch (type) {
2013-07-19 10:40:43 +00:00
case COLLADAFW::MeshPrimitive::LINES:
{
size_t prim_totface = mp->getFaceCount();
loose_edge_count += prim_totface;
break;
}
2013-07-19 10:40:43 +00:00
default:
break;
}
}
return loose_edge_count;
}
// =================================================================
// This function is copied from source/blender/editors/mesh/mesh_data.c
//
// TODO: (As discussed with sergey-) :
// Maybe move this function to blenderkernel/intern/mesh.c
// and add definition to BKE_mesh.c
// =================================================================
void MeshImporter::mesh_add_edges(Mesh *mesh, int len)
{
CustomData edata;
MEdge *medge;
int totedge;
if (len == 0)
return;
totedge = mesh->totedge + len;
/* update customdata */
CustomData_copy(&mesh->edata, &edata, CD_MASK_MESH, CD_DEFAULT, totedge);
CustomData_copy_data(&mesh->edata, &edata, 0, 0, mesh->totedge);
if (!CustomData_has_layer(&edata, CD_MEDGE))
CustomData_add_layer(&edata, CD_MEDGE, CD_CALLOC, NULL, totedge);
CustomData_free(&mesh->edata, mesh->totedge);
mesh->edata = edata;
BKE_mesh_update_customdata_pointers(mesh, false); /* new edges don't change tessellation */
/* set default flags */
medge = &mesh->medge[mesh->totedge];
for (int i = 0; i < len; i++, medge++)
medge->flag = ME_EDGEDRAW | ME_EDGERENDER | SELECT;
mesh->totedge = totedge;
}
// =================================================================
// Read all loose edges.
// Important: This function assumes that all edges from existing
// faces have already been generated and added to me->medge
// So this function MUST be called after read_faces() (see below)
// =================================================================
void MeshImporter::read_lines(COLLADAFW::Mesh *mesh, Mesh *me)
{
unsigned int loose_edge_count = get_loose_edge_count(mesh);
2012-06-12 22:05:33 +00:00
if (loose_edge_count > 0) {
unsigned int face_edge_count = me->totedge;
/* unsigned int total_edge_count = loose_edge_count + face_edge_count; */ /* UNUSED */
mesh_add_edges(me, loose_edge_count);
MEdge *med = me->medge + face_edge_count;
COLLADAFW::MeshPrimitiveArray& prim_arr = mesh->getMeshPrimitives();
for (int i = 0; i < prim_arr.getCount(); i++) {
COLLADAFW::MeshPrimitive *mp = prim_arr[i];
int type = mp->getPrimitiveType();
2012-06-27 18:29:47 +00:00
if (type == COLLADAFW::MeshPrimitive::LINES) {
unsigned int edge_count = mp->getFaceCount();
unsigned int *indices = mp->getPositionIndices().getData();
for (int j = 0; j < edge_count; j++, med++) {
2012-06-12 22:05:33 +00:00
med->bweight = 0;
med->crease = 0;
med->flag |= ME_LOOSEEDGE;
med->v1 = indices[2 * j];
med->v2 = indices[2 * j + 1];
}
}
}
}
}
// =======================================================================
// Read all faces from TRIANGLES, TRIANGLE_FANS, POLYLIST, POLYGON
// Important: This function MUST be called before read_lines()
// Otherwise we will loose all edges from faces (see read_lines() above)
//
// TODO: import uv set names
// ========================================================================
void MeshImporter::read_polys(COLLADAFW::Mesh *collada_mesh, Mesh *me)
{
unsigned int i;
allocate_poly_data(collada_mesh, me);
UVDataWrapper uvs(collada_mesh->getUVCoords());
VCOLDataWrapper vcol(collada_mesh->getColors());
MPoly *mpoly = me->mpoly;
MLoop *mloop = me->mloop;
int loop_index = 0;
MaterialIdPrimitiveArrayMap mat_prim_map;
COLLADAFW::MeshPrimitiveArray& prim_arr = collada_mesh->getMeshPrimitives();
COLLADAFW::MeshVertexData& nor = collada_mesh->getNormals();
for (i = 0; i < prim_arr.getCount(); i++) {
COLLADAFW::MeshPrimitive *mp = prim_arr[i];
// faces
size_t prim_totpoly = mp->getFaceCount();
unsigned int *position_indices = mp->getPositionIndices().getData();
unsigned int *normal_indices = mp->getNormalIndices().getData();
bool mp_has_normals = primitive_has_useable_normals(mp);
bool mp_has_faces = primitive_has_faces(mp);
int collada_meshtype = mp->getPrimitiveType();
// since we cannot set mpoly->mat_nr here, we store a portion of me->mpoly in Primitive
Primitive prim = {mpoly, 0};
// If MeshPrimitive is TRIANGLE_FANS we split it into triangles
// The first trifan vertex will be the first vertex in every triangle
// XXX The proper function of TRIANGLE_FANS is not tested!!!
// XXX In particular the handling of the normal_indices looks very wrong to me
if (collada_meshtype == COLLADAFW::MeshPrimitive::TRIANGLE_FANS) {
unsigned grouped_vertex_count = mp->getGroupedVertexElementsCount();
2012-03-28 05:03:24 +00:00
for (unsigned int group_index = 0; group_index < grouped_vertex_count; group_index++) {
unsigned int first_vertex = position_indices[0]; // Store first trifan vertex
unsigned int first_normal = normal_indices[0]; // Store first trifan vertex normal
unsigned int vertex_count = mp->getGroupedVerticesVertexCount(group_index);
2012-03-28 05:03:24 +00:00
for (unsigned int vertex_index = 0; vertex_index < vertex_count - 2; vertex_index++) {
// For each triangle store indices of its 3 vertices
unsigned int triangle_vertex_indices[3] = {first_vertex, position_indices[1], position_indices[2]};
set_poly_indices(mpoly, mloop, loop_index, triangle_vertex_indices, 3);
if (mp_has_normals) { // vertex normals, same inplementation as for the triangles
// the same for vertces normals
unsigned int vertex_normal_indices[3] = {first_normal, normal_indices[1], normal_indices[2]};
if (!is_flat_face(vertex_normal_indices, nor, 3))
mpoly->flag |= ME_SMOOTH;
normal_indices++;
}
mpoly++;
mloop += 3;
loop_index += 3;
prim.totpoly++;
2012-06-12 22:05:33 +00:00
}
// Moving cursor to the next triangle fan.
if (mp_has_normals)
normal_indices += 2;
position_indices += 2;
}
}
if (collada_meshtype == COLLADAFW::MeshPrimitive::POLYLIST ||
2018-04-16 17:08:27 +02:00
collada_meshtype == COLLADAFW::MeshPrimitive::POLYGONS ||
collada_meshtype == COLLADAFW::MeshPrimitive::TRIANGLES)
{
COLLADAFW::Polygons *mpvc = (COLLADAFW::Polygons *)mp;
unsigned int start_index = 0;
COLLADAFW::IndexListArray& index_list_array_uvcoord = mp->getUVCoordIndicesArray();
COLLADAFW::IndexListArray& index_list_array_vcolor = mp->getColorIndicesArray();
int invalid_loop_holes = 0;
for (unsigned int j = 0; j < prim_totpoly; j++) {
// Vertices in polygon:
int vcount = get_vertex_count(mpvc, j);
if (vcount < 0) {
continue; // TODO: add support for holes
}
bool broken_loop = set_poly_indices(mpoly, mloop, loop_index, position_indices, vcount);
if (broken_loop)
{
invalid_loop_holes += 1;
}
for (unsigned int uvset_index = 0; uvset_index < index_list_array_uvcoord.getCount(); uvset_index++) {
// get mtface by face index and uv set index
COLLADAFW::IndexList& index_list = *index_list_array_uvcoord[uvset_index];
MLoopUV *mloopuv = (MLoopUV *)CustomData_get_layer_named(&me->ldata, CD_MLOOPUV, index_list.getName().c_str());
if (mloopuv == NULL) {
fprintf(stderr, "Collada import: Mesh [%s] : Unknown reference to TEXCOORD [#%s].\n", me->id.name, index_list.getName().c_str() );
}
else {
set_face_uv(mloopuv+loop_index, uvs, start_index, *index_list_array_uvcoord[uvset_index], vcount);
}
}
if (mp_has_normals) {
if (!is_flat_face(normal_indices, nor, vcount))
mpoly->flag |= ME_SMOOTH;
}
if (mp->hasColorIndices()) {
int vcolor_count = index_list_array_vcolor.getCount();
for (unsigned int vcolor_index = 0; vcolor_index < vcolor_count; vcolor_index++) {
COLLADAFW::IndexList& color_index_list = *mp->getColorIndices(vcolor_index);
COLLADAFW::String colname = extract_vcolname(color_index_list.getName());
MLoopCol *mloopcol = (MLoopCol *)CustomData_get_layer_named(&me->ldata, CD_MLOOPCOL, colname.c_str());
if (mloopcol == NULL) {
fprintf(stderr, "Collada import: Mesh [%s] : Unknown reference to VCOLOR [#%s].\n", me->id.name, color_index_list.getName().c_str());
}
else {
set_vcol(mloopcol + loop_index, vcol, start_index, color_index_list, vcount);
}
}
}
mpoly++;
mloop += vcount;
loop_index += vcount;
start_index += vcount;
prim.totpoly++;
if (mp_has_normals)
normal_indices += vcount;
position_indices += vcount;
}
if (invalid_loop_holes > 0)
{
fprintf(stderr, "Collada import: Mesh [%s] : contains %d unsupported loops (holes).\n", me->id.name, invalid_loop_holes);
}
}
else if (collada_meshtype == COLLADAFW::MeshPrimitive::LINES) {
continue; // read the lines later after all the rest is done
}
if (mp_has_faces)
mat_prim_map[mp->getMaterialId()].push_back(prim);
}
geom_uid_mat_mapping_map[collada_mesh->getUniqueId()] = mat_prim_map;
}
void MeshImporter::get_vector(float v[3], COLLADAFW::MeshVertexData& arr, int i, int stride)
{
i *= stride;
switch (arr.getType()) {
2012-06-12 22:05:33 +00:00
case COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT:
{
2012-06-12 22:05:33 +00:00
COLLADAFW::ArrayPrimitiveType<float> *values = arr.getFloatValues();
if (values->empty()) return;
v[0] = (*values)[i++];
v[1] = (*values)[i++];
if (stride>=3) {
v[2] = (*values)[i];
}
else {
v[2] = 0.0f;
}
}
break;
2012-06-12 22:05:33 +00:00
case COLLADAFW::MeshVertexData::DATA_TYPE_DOUBLE:
{
2012-06-12 22:05:33 +00:00
COLLADAFW::ArrayPrimitiveType<double> *values = arr.getDoubleValues();
if (values->empty()) return;
v[0] = (float)(*values)[i++];
v[1] = (float)(*values)[i++];
if (stride >= 3) {
v[2] = (float)(*values)[i];
}
else {
v[2] = 0.0f;
}
}
break;
2012-06-12 22:05:33 +00:00
default:
break;
}
}
bool MeshImporter::is_flat_face(unsigned int *nind, COLLADAFW::MeshVertexData& nor, int count)
{
float a[3], b[3];
get_vector(a, nor, *nind, 3);
normalize_v3(a);
nind++;
for (int i = 1; i < count; i++, nind++) {
get_vector(b, nor, *nind, 3);
normalize_v3(b);
float dp = dot_v3v3(a, b);
if (dp < 0.99999f || dp > 1.00001f)
return false;
}
return true;
}
Object *MeshImporter::get_object_by_geom_uid(const COLLADAFW::UniqueId& geom_uid)
{
if (uid_object_map.find(geom_uid) != uid_object_map.end())
return uid_object_map[geom_uid];
return NULL;
}
Mesh *MeshImporter::get_mesh_by_geom_uid(const COLLADAFW::UniqueId& mesh_uid)
{
if (uid_mesh_map.find(mesh_uid) != uid_mesh_map.end())
return uid_mesh_map[mesh_uid];
return NULL;
}
std::string *MeshImporter::get_geometry_name(const std::string &mesh_name)
{
if (this->mesh_geom_map.find(mesh_name) != this->mesh_geom_map.end())
return &this->mesh_geom_map[mesh_name];
return NULL;
}
/**
* this function checks if both objects have the same
* materials assigned to Object (in the same order)
* returns true if condition matches, otherwise false;
**/
static bool bc_has_same_material_configuration(Object *ob1, Object *ob2)
{
if (ob1->totcol != ob2->totcol) return false; // not same number of materials
if (ob1->totcol == 0) return false; // no material at all
for (int index=0; index < ob1->totcol; index++) {
if (ob1->matbits[index] != ob2->matbits[index]) return false; // shouldn't happen
if (ob1->matbits[index] == 0) return false; // shouldn't happen
if (ob1->mat[index] != ob2->mat[index]) return false; // different material assignment
}
return true;
}
/**
*
* Caution here: This code assumes that all materials are assigned to Object
* and no material is assigned to Data.
* That is true right after the objects have been imported.
*
**/
static void bc_copy_materials_to_data(Object *ob, Mesh *me)
{
for (int index = 0; index < ob->totcol; index++) {
ob->matbits[index] = 0;
me->mat[index] = ob->mat[index];
}
}
/**
*
* Remove all references to materials from the object
*
**/
static void bc_remove_materials_from_object(Object *ob, Mesh *me)
{
for (int index = 0; index < ob->totcol; index++) {
ob->matbits[index] = 0;
ob->mat[index] = NULL;
}
}
/**
* Returns the list of Users of the given Mesh object.
* Note: This function uses the object user flag to control
* which objects have already been processed.
**/
std::vector<Object *> MeshImporter::get_all_users_of(Mesh *reference_mesh)
{
std::vector<Object *> mesh_users;
for (std::vector<Object *>::iterator it = imported_objects.begin();
it != imported_objects.end(); ++it)
{
Object *ob = (*it);
if (bc_is_marked(ob)) {
bc_remove_mark(ob);
Mesh *me = (Mesh *) ob->data;
if (me == reference_mesh)
mesh_users.push_back(ob);
}
}
return mesh_users;
}
/**
*
* During import all materials have been assigned to Object.
* Now we iterate over the imported objects and optimize
* the assignments as follows:
*
* for each imported geometry:
* if number of users is 1:
* get the user (object)
* move the materials from Object to Data
* else:
* determine which materials are assigned to the first user
* check if all other users have the same materials in the same order
* if the check is positive:
* Add the materials of the first user to the geometry
* adjust all other users accordingly.
*
**/
void MeshImporter::optimize_material_assignements()
{
for (std::vector<Object *>::iterator it = imported_objects.begin();
it != imported_objects.end(); ++it)
{
Object *ob = (*it);
Mesh *me = (Mesh *) ob->data;
if (me->id.us==1) {
2012-12-29 01:54:58 +00:00
bc_copy_materials_to_data(ob, me);
bc_remove_materials_from_object(ob, me);
bc_remove_mark(ob);
}
else if (me->id.us > 1)
{
bool can_move = true;
std::vector<Object *> mesh_users = get_all_users_of(me);
if (mesh_users.size() > 1)
{
Object *ref_ob = mesh_users[0];
for (int index = 1; index < mesh_users.size(); index++) {
if (!bc_has_same_material_configuration(ref_ob, mesh_users[index])) {
can_move = false;
break;
}
}
if (can_move) {
2012-12-29 01:54:58 +00:00
bc_copy_materials_to_data(ref_ob, me);
for (int index = 0; index < mesh_users.size(); index++) {
Object *object = mesh_users[index];
2012-12-29 01:54:58 +00:00
bc_remove_materials_from_object(object, me);
bc_remove_mark(object);
}
}
}
}
}
}
/**
* We do not know in advance which objects will share geometries.
* And we do not know either if the objects which share geometries
* come along with different materials. So we first create the objects
* and assign the materials to Object, then in a later cleanup we decide
* which materials shall be moved to the created geometries. Also see
* optimize_material_assignements() above.
*/
void MeshImporter::assign_material_to_geom(
COLLADAFW::MaterialBinding cmaterial,
std::map<COLLADAFW::UniqueId, Material *>& uid_material_map,
Object *ob, const COLLADAFW::UniqueId *geom_uid,
Remove Blender Internal and legacy viewport from Blender 2.8. Brecht authored this commit, but he gave me the honours to actually do it. Here it goes; Blender Internal. Bye bye, you did great! * Point density, voxel data, ocean, environment map textures were removed, as these only worked within BI rendering. Note that the ocean modifier and the Cycles point density shader node continue to work. * Dynamic paint using material shading was removed, as this only worked with BI. If we ever wanted to support this again probably it should go through the baking API. * GPU shader export through the Python API was removed. This only worked for the old BI GLSL shaders, which no longer exists. Doing something similar for Eevee would be significantly more complicated because it uses a lot of multiplass rendering and logic outside the shader, it's probably impractical. * Collada material import / export code is mostly gone, as it only worked for BI materials. We need to add Cycles / Eevee material support at some point. * The mesh noise operator was removed since it only worked with BI material texture slots. A displacement modifier can be used instead. * The delete texture paint slot operator was removed since it only worked for BI material texture slots. Could be added back with node support. * Not all legacy viewport features are supported in the new viewport, but their code was removed. If we need to bring anything back we can look at older git revisions. * There is some legacy viewport code that I could not remove yet, and some that I probably missed. * Shader node execution code was left mostly intact, even though it is not used anywhere now. We may eventually use this to replace the texture nodes with Cycles / Eevee shader nodes. * The Cycles Bake panel now includes settings for baking multires normal and displacement maps. The underlying code needs to be merged properly, and we plan to add back support for multires AO baking and add support to Cycles baking for features like vertex color, displacement, and other missing baking features. * This commit removes DNA and the Python API for BI material, lamp, world and scene settings. This breaks a lot of addons. * There is more DNA that can be removed or renamed, where Cycles or Eevee are reusing some old BI properties but the names are not really correct anymore. * Texture slots for materials, lamps and world were removed. They remain for brushes, particles and freestyle linestyles. * 'BLENDER_RENDER' remains in the COMPAT_ENGINES of UI panels. Cycles and other renderers use this to find all panels to show, minus a few panels that they have their own replacement for.
2018-04-19 17:34:44 +02:00
short mat_index)
{
const COLLADAFW::UniqueId& ma_uid = cmaterial.getReferencedMaterial();
// do we know this material?
if (uid_material_map.find(ma_uid) == uid_material_map.end()) {
fprintf(stderr, "Cannot find material by UID.\n");
return;
}
// first time we get geom_uid, ma_uid pair. Save for later check.
materials_mapped_to_geom.insert(std::pair<COLLADAFW::UniqueId, COLLADAFW::UniqueId>(*geom_uid, ma_uid));
Material *ma = uid_material_map[ma_uid];
// Attention! This temporarily assigns material to object on purpose!
// See note above.
ob->actcol=0;
assign_material(m_bmain, ob, ma, mat_index + 1, BKE_MAT_ASSIGN_OBJECT);
MaterialIdPrimitiveArrayMap& mat_prim_map = geom_uid_mat_mapping_map[*geom_uid];
COLLADAFW::MaterialId mat_id = cmaterial.getMaterialId();
// assign material indices to mesh faces
if (mat_prim_map.find(mat_id) != mat_prim_map.end()) {
std::vector<Primitive>& prims = mat_prim_map[mat_id];
std::vector<Primitive>::iterator it;
for (it = prims.begin(); it != prims.end(); it++) {
Primitive& prim = *it;
MPoly *mpoly = prim.mpoly;
Remove Blender Internal and legacy viewport from Blender 2.8. Brecht authored this commit, but he gave me the honours to actually do it. Here it goes; Blender Internal. Bye bye, you did great! * Point density, voxel data, ocean, environment map textures were removed, as these only worked within BI rendering. Note that the ocean modifier and the Cycles point density shader node continue to work. * Dynamic paint using material shading was removed, as this only worked with BI. If we ever wanted to support this again probably it should go through the baking API. * GPU shader export through the Python API was removed. This only worked for the old BI GLSL shaders, which no longer exists. Doing something similar for Eevee would be significantly more complicated because it uses a lot of multiplass rendering and logic outside the shader, it's probably impractical. * Collada material import / export code is mostly gone, as it only worked for BI materials. We need to add Cycles / Eevee material support at some point. * The mesh noise operator was removed since it only worked with BI material texture slots. A displacement modifier can be used instead. * The delete texture paint slot operator was removed since it only worked for BI material texture slots. Could be added back with node support. * Not all legacy viewport features are supported in the new viewport, but their code was removed. If we need to bring anything back we can look at older git revisions. * There is some legacy viewport code that I could not remove yet, and some that I probably missed. * Shader node execution code was left mostly intact, even though it is not used anywhere now. We may eventually use this to replace the texture nodes with Cycles / Eevee shader nodes. * The Cycles Bake panel now includes settings for baking multires normal and displacement maps. The underlying code needs to be merged properly, and we plan to add back support for multires AO baking and add support to Cycles baking for features like vertex color, displacement, and other missing baking features. * This commit removes DNA and the Python API for BI material, lamp, world and scene settings. This breaks a lot of addons. * There is more DNA that can be removed or renamed, where Cycles or Eevee are reusing some old BI properties but the names are not really correct anymore. * Texture slots for materials, lamps and world were removed. They remain for brushes, particles and freestyle linestyles. * 'BLENDER_RENDER' remains in the COMPAT_ENGINES of UI panels. Cycles and other renderers use this to find all panels to show, minus a few panels that they have their own replacement for.
2018-04-19 17:34:44 +02:00
for (int i = 0; i < prim.totpoly; i++, mpoly++) {
mpoly->mat_nr = mat_index;
}
}
}
}
Object *MeshImporter::create_mesh_object(COLLADAFW::Node *node, COLLADAFW::InstanceGeometry *geom,
2012-06-12 22:05:33 +00:00
bool isController,
Remove Blender Internal and legacy viewport from Blender 2.8. Brecht authored this commit, but he gave me the honours to actually do it. Here it goes; Blender Internal. Bye bye, you did great! * Point density, voxel data, ocean, environment map textures were removed, as these only worked within BI rendering. Note that the ocean modifier and the Cycles point density shader node continue to work. * Dynamic paint using material shading was removed, as this only worked with BI. If we ever wanted to support this again probably it should go through the baking API. * GPU shader export through the Python API was removed. This only worked for the old BI GLSL shaders, which no longer exists. Doing something similar for Eevee would be significantly more complicated because it uses a lot of multiplass rendering and logic outside the shader, it's probably impractical. * Collada material import / export code is mostly gone, as it only worked for BI materials. We need to add Cycles / Eevee material support at some point. * The mesh noise operator was removed since it only worked with BI material texture slots. A displacement modifier can be used instead. * The delete texture paint slot operator was removed since it only worked for BI material texture slots. Could be added back with node support. * Not all legacy viewport features are supported in the new viewport, but their code was removed. If we need to bring anything back we can look at older git revisions. * There is some legacy viewport code that I could not remove yet, and some that I probably missed. * Shader node execution code was left mostly intact, even though it is not used anywhere now. We may eventually use this to replace the texture nodes with Cycles / Eevee shader nodes. * The Cycles Bake panel now includes settings for baking multires normal and displacement maps. The underlying code needs to be merged properly, and we plan to add back support for multires AO baking and add support to Cycles baking for features like vertex color, displacement, and other missing baking features. * This commit removes DNA and the Python API for BI material, lamp, world and scene settings. This breaks a lot of addons. * There is more DNA that can be removed or renamed, where Cycles or Eevee are reusing some old BI properties but the names are not really correct anymore. * Texture slots for materials, lamps and world were removed. They remain for brushes, particles and freestyle linestyles. * 'BLENDER_RENDER' remains in the COMPAT_ENGINES of UI panels. Cycles and other renderers use this to find all panels to show, minus a few panels that they have their own replacement for.
2018-04-19 17:34:44 +02:00
std::map<COLLADAFW::UniqueId, Material *>& uid_material_map)
{
const COLLADAFW::UniqueId *geom_uid = &geom->getInstanciatedObjectId();
// check if node instantiates controller or geometry
if (isController) {
geom_uid = armature_importer->get_geometry_uid(*geom_uid);
if (!geom_uid) {
fprintf(stderr, "Couldn't find a mesh UID by controller's UID.\n");
return NULL;
}
}
else {
if (uid_mesh_map.find(*geom_uid) == uid_mesh_map.end()) {
// this could happen if a mesh was not created
// (e.g. if it contains unsupported geometry)
fprintf(stderr, "Couldn't find a mesh by UID.\n");
return NULL;
}
}
if (!uid_mesh_map[*geom_uid]) return NULL;
// name Object
const std::string& id = node->getName().size() ? node->getName() : node->getOriginalId();
2012-06-12 22:05:33 +00:00
const char *name = (id.length()) ? id.c_str() : NULL;
// add object
Object *ob = bc_add_object(m_bmain, scene, view_layer, OB_MESH, name);
bc_set_mark(ob); // used later for material assignment optimization
// store object pointer for ArmatureImporter
uid_object_map[*geom_uid] = ob;
imported_objects.push_back(ob);
// replace ob->data freeing the old one
2012-06-12 22:05:33 +00:00
Mesh *old_mesh = (Mesh *)ob->data;
Mesh *new_mesh = uid_mesh_map[*geom_uid];
BKE_mesh_assign_object(m_bmain, ob, new_mesh);
BKE_mesh_calc_normals(new_mesh);
id_us_plus(&old_mesh->id); /* Because BKE_mesh_assign_object would have already decreased it... */
BKE_libblock_free_us(m_bmain, old_mesh);
COLLADAFW::MaterialBindingArray& mat_array =
2012-06-12 22:05:33 +00:00
geom->getMaterialBindings();
// loop through geom's materials
2012-06-12 22:05:33 +00:00
for (unsigned int i = 0; i < mat_array.getCount(); i++) {
if (mat_array[i].getReferencedMaterial().isValid()) {
assign_material_to_geom(
mat_array[i], uid_material_map, ob, geom_uid,
Remove Blender Internal and legacy viewport from Blender 2.8. Brecht authored this commit, but he gave me the honours to actually do it. Here it goes; Blender Internal. Bye bye, you did great! * Point density, voxel data, ocean, environment map textures were removed, as these only worked within BI rendering. Note that the ocean modifier and the Cycles point density shader node continue to work. * Dynamic paint using material shading was removed, as this only worked with BI. If we ever wanted to support this again probably it should go through the baking API. * GPU shader export through the Python API was removed. This only worked for the old BI GLSL shaders, which no longer exists. Doing something similar for Eevee would be significantly more complicated because it uses a lot of multiplass rendering and logic outside the shader, it's probably impractical. * Collada material import / export code is mostly gone, as it only worked for BI materials. We need to add Cycles / Eevee material support at some point. * The mesh noise operator was removed since it only worked with BI material texture slots. A displacement modifier can be used instead. * The delete texture paint slot operator was removed since it only worked for BI material texture slots. Could be added back with node support. * Not all legacy viewport features are supported in the new viewport, but their code was removed. If we need to bring anything back we can look at older git revisions. * There is some legacy viewport code that I could not remove yet, and some that I probably missed. * Shader node execution code was left mostly intact, even though it is not used anywhere now. We may eventually use this to replace the texture nodes with Cycles / Eevee shader nodes. * The Cycles Bake panel now includes settings for baking multires normal and displacement maps. The underlying code needs to be merged properly, and we plan to add back support for multires AO baking and add support to Cycles baking for features like vertex color, displacement, and other missing baking features. * This commit removes DNA and the Python API for BI material, lamp, world and scene settings. This breaks a lot of addons. * There is more DNA that can be removed or renamed, where Cycles or Eevee are reusing some old BI properties but the names are not really correct anymore. * Texture slots for materials, lamps and world were removed. They remain for brushes, particles and freestyle linestyles. * 'BLENDER_RENDER' remains in the COMPAT_ENGINES of UI panels. Cycles and other renderers use this to find all panels to show, minus a few panels that they have their own replacement for.
2018-04-19 17:34:44 +02:00
i);
}
else {
2011-03-08 20:41:43 +00:00
fprintf(stderr, "invalid referenced material for %s\n", mat_array[i].getName().c_str());
}
}
return ob;
}
// create a mesh storing a pointer in a map so it can be retrieved later by geometry UID
2012-06-12 22:05:33 +00:00
bool MeshImporter::write_geometry(const COLLADAFW::Geometry *geom)
{
if (geom->getType() != COLLADAFW::Geometry::GEO_TYPE_MESH) {
// TODO: report warning
fprintf(stderr, "Mesh type %s is not supported\n", bc_geomTypeToStr(geom->getType()));
return true;
}
2012-06-12 22:05:33 +00:00
COLLADAFW::Mesh *mesh = (COLLADAFW::Mesh *)geom;
if (!is_nice_mesh(mesh)) {
fprintf(stderr, "Ignoring mesh %s\n", bc_get_dae_name(mesh).c_str());
return true;
}
const std::string& str_geom_id = mesh->getName().size() ? mesh->getName() : mesh->getOriginalId();
Mesh *me = BKE_mesh_add(m_bmain, (char *)str_geom_id.c_str());
id_us_min(&me->id); // is already 1 here, but will be set later in BKE_mesh_assign_object
// store the Mesh pointer to link it later with an Object
// mesh_geom_map needed to map mesh to its geometry name (for shape key naming)
this->uid_mesh_map[mesh->getUniqueId()] = me;
this->mesh_geom_map[std::string(me->id.name)] = str_geom_id;
read_vertices(mesh, me);
read_polys(mesh, me);
// must validate before calculating edges
BKE_mesh_calc_normals(me);
BKE_mesh_validate(me, false, false);
// validation does this
// BKE_mesh_calc_edges(me, false, false);
2011-10-28 06:12:00 +00:00
// read_lines() must be called after the face edges have been generated.
// Otherwise the loose edges will be silently deleted again.
read_lines(mesh, me);
return true;
}