2015-05-19 03:48:29 +00:00
|
|
|
/*
|
|
|
|
Open Asset Import Library (assimp)
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
|
2020-01-20 13:53:12 +00:00
|
|
|
Copyright (c) 2006-2020, assimp team
|
2018-01-28 18:42:05 +00:00
|
|
|
|
2017-05-09 17:57:36 +00:00
|
|
|
|
2015-05-19 03:48:29 +00:00
|
|
|
All rights reserved.
|
|
|
|
|
2015-05-19 03:52:10 +00:00
|
|
|
Redistribution and use of this software in source and binary forms,
|
|
|
|
with or without modification, are permitted provided that the
|
2015-05-19 03:48:29 +00:00
|
|
|
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.
|
|
|
|
|
2015-05-19 03:52:10 +00:00
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
2015-05-19 03:48:29 +00:00
|
|
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
2015-05-19 03:52:10 +00:00
|
|
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
2015-05-19 03:48:29 +00:00
|
|
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
2015-05-19 03:52:10 +00:00
|
|
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
2015-05-19 03:48:29 +00:00
|
|
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
2015-05-19 03:52:10 +00:00
|
|
|
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
|
2015-05-19 03:48:29 +00:00
|
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
2018-01-06 00:18:33 +00:00
|
|
|
#include <assimp/Subdivision.h>
|
2017-06-21 13:24:06 +00:00
|
|
|
#include <assimp/SceneCombiner.h>
|
2018-01-06 00:18:33 +00:00
|
|
|
#include <assimp/SpatialSort.h>
|
|
|
|
#include <assimp/Vertex.h>
|
2016-11-21 22:54:39 +00:00
|
|
|
#include <assimp/ai_assert.h>
|
2019-06-10 21:26:00 +00:00
|
|
|
|
|
|
|
#include "PostProcessing/ProcessHelper.h"
|
|
|
|
|
2015-05-19 03:48:29 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
using namespace Assimp;
|
|
|
|
void mydummy() {}
|
|
|
|
|
2020-03-18 18:31:30 +00:00
|
|
|
#ifdef _WIN32
|
|
|
|
# pragma warning( disable : 4709 )
|
|
|
|
#endif // _WIN32
|
2015-05-19 03:48:29 +00:00
|
|
|
// ------------------------------------------------------------------------------------------------
|
2015-05-19 03:52:10 +00:00
|
|
|
/** Subdivider stub class to implement the Catmull-Clarke subdivision algorithm. The
|
2015-05-19 03:48:29 +00:00
|
|
|
* implementation is basing on recursive refinement. Directly evaluating the result is also
|
|
|
|
* possible and much quicker, but it depends on lengthy matrix lookup tables. */
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
2019-06-10 21:26:00 +00:00
|
|
|
class CatmullClarkSubdivider : public Subdivider {
|
2015-05-19 03:48:29 +00:00
|
|
|
public:
|
2015-05-19 03:57:13 +00:00
|
|
|
void Subdivide (aiMesh* mesh, aiMesh*& out, unsigned int num, bool discard_input);
|
|
|
|
void Subdivide (aiMesh** smesh, size_t nmesh,
|
|
|
|
aiMesh** out, unsigned int num, bool discard_input);
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/** Intermediate description of an edge between two corners of a polygon*/
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
struct Edge
|
|
|
|
{
|
|
|
|
Edge()
|
|
|
|
: ref(0)
|
|
|
|
{}
|
|
|
|
Vertex edge_point, midpoint;
|
|
|
|
unsigned int ref;
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef std::vector<unsigned int> UIntVector;
|
|
|
|
typedef std::map<uint64_t,Edge> EdgeMap;
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Hashing function to derive an index into an #EdgeMap from two given
|
|
|
|
// 'unsigned int' vertex coordinates (!!distinct coordinates - same
|
|
|
|
// vertex position == same index!!).
|
|
|
|
// NOTE - this leads to rare hash collisions if a) sizeof(unsigned int)>4
|
|
|
|
// and (id[0]>2^32-1 or id[0]>2^32-1).
|
|
|
|
// MAKE_EDGE_HASH() uses temporaries, so INIT_EDGE_HASH() needs to be put
|
|
|
|
// at the head of every function which is about to use MAKE_EDGE_HASH().
|
|
|
|
// Reason is that the hash is that hash construction needs to hold the
|
|
|
|
// invariant id0<id1 to identify an edge - else two hashes would refer
|
|
|
|
// to the same edge.
|
|
|
|
// ---------------------------------------------------------------------------
|
2015-05-19 03:48:29 +00:00
|
|
|
#define MAKE_EDGE_HASH(id0,id1) (eh_tmp0__=id0,eh_tmp1__=id1,\
|
2015-05-19 03:57:13 +00:00
|
|
|
(eh_tmp0__<eh_tmp1__?std::swap(eh_tmp0__,eh_tmp1__):mydummy()),(uint64_t)eh_tmp0__^((uint64_t)eh_tmp1__<<32u))
|
2015-05-19 03:48:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
#define INIT_EDGE_HASH_TEMPORARIES()\
|
2015-05-19 03:57:13 +00:00
|
|
|
unsigned int eh_tmp0__, eh_tmp1__;
|
2015-05-19 03:48:29 +00:00
|
|
|
|
|
|
|
private:
|
2015-05-19 03:57:13 +00:00
|
|
|
void InternSubdivide (const aiMesh* const * smesh,
|
|
|
|
size_t nmesh,aiMesh** out, unsigned int num);
|
2015-05-19 03:48:29 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
// Construct a subdivider of a specific type
|
|
|
|
Subdivider* Subdivider::Create (Algorithm algo)
|
|
|
|
{
|
2015-05-19 03:57:13 +00:00
|
|
|
switch (algo)
|
|
|
|
{
|
|
|
|
case CATMULL_CLARKE:
|
|
|
|
return new CatmullClarkSubdivider();
|
|
|
|
};
|
|
|
|
|
|
|
|
ai_assert(false);
|
|
|
|
return NULL; // shouldn't happen
|
2015-05-19 03:48:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
// Call the Catmull Clark subdivision algorithm for one mesh
|
|
|
|
void CatmullClarkSubdivider::Subdivide (
|
2015-05-19 03:57:13 +00:00
|
|
|
aiMesh* mesh,
|
|
|
|
aiMesh*& out,
|
|
|
|
unsigned int num,
|
|
|
|
bool discard_input
|
|
|
|
)
|
2015-05-19 03:48:29 +00:00
|
|
|
{
|
2016-11-21 22:54:39 +00:00
|
|
|
ai_assert(mesh != out);
|
|
|
|
|
2015-05-19 03:57:13 +00:00
|
|
|
Subdivide(&mesh,1,&out,num,discard_input);
|
2015-05-19 03:48:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
// Call the Catmull Clark subdivision algorithm for multiple meshes
|
|
|
|
void CatmullClarkSubdivider::Subdivide (
|
2015-05-19 03:57:13 +00:00
|
|
|
aiMesh** smesh,
|
|
|
|
size_t nmesh,
|
|
|
|
aiMesh** out,
|
|
|
|
unsigned int num,
|
|
|
|
bool discard_input
|
|
|
|
)
|
2015-05-19 03:48:29 +00:00
|
|
|
{
|
2016-11-21 22:54:39 +00:00
|
|
|
ai_assert( NULL != smesh );
|
|
|
|
ai_assert( NULL != out );
|
2015-05-19 03:57:13 +00:00
|
|
|
|
|
|
|
// course, both regions may not overlap
|
2016-11-21 22:54:39 +00:00
|
|
|
ai_assert(smesh<out || smesh+nmesh>out+nmesh);
|
2015-05-19 03:57:13 +00:00
|
|
|
if (!num) {
|
|
|
|
// No subdivision at all. Need to copy all the meshes .. argh.
|
|
|
|
if (discard_input) {
|
|
|
|
for (size_t s = 0; s < nmesh; ++s) {
|
|
|
|
out[s] = smesh[s];
|
|
|
|
smesh[s] = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
for (size_t s = 0; s < nmesh; ++s) {
|
|
|
|
SceneCombiner::Copy(out+s,smesh[s]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<aiMesh*> inmeshes;
|
|
|
|
std::vector<aiMesh*> outmeshes;
|
|
|
|
std::vector<unsigned int> maptbl;
|
|
|
|
|
|
|
|
inmeshes.reserve(nmesh);
|
|
|
|
outmeshes.reserve(nmesh);
|
|
|
|
maptbl.reserve(nmesh);
|
|
|
|
|
|
|
|
// Remove pure line and point meshes from the working set to reduce the
|
|
|
|
// number of edge cases the subdivider is forced to deal with. Line and
|
|
|
|
// point meshes are simply passed through.
|
|
|
|
for (size_t s = 0; s < nmesh; ++s) {
|
|
|
|
aiMesh* i = smesh[s];
|
|
|
|
// FIX - mPrimitiveTypes might not yet be initialized
|
|
|
|
if (i->mPrimitiveTypes && (i->mPrimitiveTypes & (aiPrimitiveType_LINE|aiPrimitiveType_POINT))==i->mPrimitiveTypes) {
|
2020-05-15 11:59:48 +00:00
|
|
|
ASSIMP_LOG_VERBOSE_DEBUG("Catmull-Clark Subdivider: Skipping pure line/point mesh");
|
2015-05-19 03:57:13 +00:00
|
|
|
|
|
|
|
if (discard_input) {
|
|
|
|
out[s] = i;
|
|
|
|
smesh[s] = NULL;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
SceneCombiner::Copy(out+s,i);
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
outmeshes.push_back(NULL);inmeshes.push_back(i);
|
2016-11-27 02:37:32 +00:00
|
|
|
maptbl.push_back(static_cast<unsigned int>(s));
|
2015-05-19 03:57:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Do the actual subdivision on the preallocated storage. InternSubdivide
|
|
|
|
// *always* assumes that enough storage is available, it does not bother
|
|
|
|
// checking any ranges.
|
|
|
|
ai_assert(inmeshes.size()==outmeshes.size()&&inmeshes.size()==maptbl.size());
|
|
|
|
if (inmeshes.empty()) {
|
2018-04-20 14:23:24 +00:00
|
|
|
ASSIMP_LOG_WARN("Catmull-Clark Subdivider: Pure point/line scene, I can't do anything");
|
2015-05-19 03:57:13 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
InternSubdivide(&inmeshes.front(),inmeshes.size(),&outmeshes.front(),num);
|
|
|
|
for (unsigned int i = 0; i < maptbl.size(); ++i) {
|
2018-04-20 14:23:24 +00:00
|
|
|
ai_assert(nullptr != outmeshes[i]);
|
2015-05-19 03:57:13 +00:00
|
|
|
out[maptbl[i]] = outmeshes[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (discard_input) {
|
|
|
|
for (size_t s = 0; s < nmesh; ++s) {
|
|
|
|
delete smesh[s];
|
|
|
|
}
|
|
|
|
}
|
2015-05-19 03:48:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
2015-05-19 03:52:10 +00:00
|
|
|
// Note - this is an implementation of the standard (recursive) Cm-Cl algorithm without further
|
2015-05-19 03:48:29 +00:00
|
|
|
// optimizations (except we're using some nice LUTs). A description of the algorithm can be found
|
|
|
|
// here: http://en.wikipedia.org/wiki/Catmull-Clark_subdivision_surface
|
|
|
|
//
|
|
|
|
// The code is mostly O(n), however parts are O(nlogn) which is therefore the algorithm's
|
|
|
|
// expected total runtime complexity. The implementation is able to work in-place on the same
|
|
|
|
// mesh arrays. Calling #InternSubdivide() directly is not encouraged. The code can operate
|
2015-05-19 03:52:10 +00:00
|
|
|
// in-place unless 'smesh' and 'out' are equal (no strange overlaps or reorderings).
|
2015-05-19 03:48:29 +00:00
|
|
|
// Previous data is replaced/deleted then.
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
void CatmullClarkSubdivider::InternSubdivide (
|
2015-05-19 03:57:13 +00:00
|
|
|
const aiMesh* const * smesh,
|
|
|
|
size_t nmesh,
|
|
|
|
aiMesh** out,
|
|
|
|
unsigned int num
|
|
|
|
)
|
2015-05-19 03:48:29 +00:00
|
|
|
{
|
2015-05-19 03:57:13 +00:00
|
|
|
ai_assert(NULL != smesh && NULL != out);
|
|
|
|
INIT_EDGE_HASH_TEMPORARIES();
|
2015-05-19 03:48:29 +00:00
|
|
|
|
2015-05-19 03:57:13 +00:00
|
|
|
// no subdivision requested or end of recursive refinement
|
|
|
|
if (!num) {
|
|
|
|
return;
|
|
|
|
}
|
2015-05-19 03:48:29 +00:00
|
|
|
|
2015-05-19 03:57:13 +00:00
|
|
|
UIntVector maptbl;
|
|
|
|
SpatialSort spatial;
|
2015-05-19 03:48:29 +00:00
|
|
|
|
2015-05-19 03:57:13 +00:00
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
// 0. Offset table to index all meshes continuously, generate a spatially
|
|
|
|
// sorted representation of all vertices in all meshes.
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
typedef std::pair<unsigned int,unsigned int> IntPair;
|
|
|
|
std::vector<IntPair> moffsets(nmesh);
|
|
|
|
unsigned int totfaces = 0, totvert = 0;
|
|
|
|
for (size_t t = 0; t < nmesh; ++t) {
|
|
|
|
const aiMesh* mesh = smesh[t];
|
2015-05-19 03:48:29 +00:00
|
|
|
|
2015-05-19 03:57:13 +00:00
|
|
|
spatial.Append(mesh->mVertices,mesh->mNumVertices,sizeof(aiVector3D),false);
|
|
|
|
moffsets[t] = IntPair(totfaces,totvert);
|
2015-05-19 03:48:29 +00:00
|
|
|
|
2015-05-19 03:57:13 +00:00
|
|
|
totfaces += mesh->mNumFaces;
|
|
|
|
totvert += mesh->mNumVertices;
|
|
|
|
}
|
2015-05-19 03:48:29 +00:00
|
|
|
|
2015-05-19 03:57:13 +00:00
|
|
|
spatial.Finalize();
|
|
|
|
const unsigned int num_unique = spatial.GenerateMappingTable(maptbl,ComputePositionEpsilon(smesh,nmesh));
|
2015-05-19 03:48:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
#define FLATTEN_VERTEX_IDX(mesh_idx, vert_idx) (moffsets[mesh_idx].second+vert_idx)
|
|
|
|
#define FLATTEN_FACE_IDX(mesh_idx, face_idx) (moffsets[mesh_idx].first+face_idx)
|
|
|
|
|
2015-05-19 03:57:13 +00:00
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
// 1. Compute the centroid point for all faces
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
std::vector<Vertex> centroids(totfaces);
|
|
|
|
unsigned int nfacesout = 0;
|
|
|
|
for (size_t t = 0, n = 0; t < nmesh; ++t) {
|
|
|
|
const aiMesh* mesh = smesh[t];
|
|
|
|
for (unsigned int i = 0; i < mesh->mNumFaces;++i,++n)
|
|
|
|
{
|
|
|
|
const aiFace& face = mesh->mFaces[i];
|
|
|
|
Vertex& c = centroids[n];
|
|
|
|
|
|
|
|
for (unsigned int a = 0; a < face.mNumIndices;++a) {
|
|
|
|
c += Vertex(mesh,face.mIndices[a]);
|
|
|
|
}
|
|
|
|
|
|
|
|
c /= static_cast<float>(face.mNumIndices);
|
|
|
|
nfacesout += face.mNumIndices;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
// we want edges to go away before the recursive calls so begin a new scope
|
|
|
|
EdgeMap edges;
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
// 2. Set each edge point to be the average of all neighbouring
|
|
|
|
// face points and original points. Every edge exists twice
|
|
|
|
// if there is a neighboring face.
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
for (size_t t = 0; t < nmesh; ++t) {
|
|
|
|
const aiMesh* mesh = smesh[t];
|
|
|
|
|
|
|
|
for (unsigned int i = 0; i < mesh->mNumFaces;++i) {
|
|
|
|
const aiFace& face = mesh->mFaces[i];
|
|
|
|
|
|
|
|
for (unsigned int p =0; p< face.mNumIndices; ++p) {
|
|
|
|
const unsigned int id[] = {
|
|
|
|
face.mIndices[p],
|
|
|
|
face.mIndices[p==face.mNumIndices-1?0:p+1]
|
|
|
|
};
|
|
|
|
const unsigned int mp[] = {
|
|
|
|
maptbl[FLATTEN_VERTEX_IDX(t,id[0])],
|
|
|
|
maptbl[FLATTEN_VERTEX_IDX(t,id[1])]
|
|
|
|
};
|
|
|
|
|
|
|
|
Edge& e = edges[MAKE_EDGE_HASH(mp[0],mp[1])];
|
|
|
|
e.ref++;
|
|
|
|
if (e.ref<=2) {
|
|
|
|
if (e.ref==1) { // original points (end points) - add only once
|
|
|
|
e.edge_point = e.midpoint = Vertex(mesh,id[0])+Vertex(mesh,id[1]);
|
|
|
|
e.midpoint *= 0.5f;
|
|
|
|
}
|
|
|
|
e.edge_point += centroids[FLATTEN_FACE_IDX(t,i)];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
// 3. Normalize edge points
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
{unsigned int bad_cnt = 0;
|
|
|
|
for (EdgeMap::iterator it = edges.begin(); it != edges.end(); ++it) {
|
|
|
|
if ((*it).second.ref < 2) {
|
|
|
|
ai_assert((*it).second.ref);
|
|
|
|
++bad_cnt;
|
|
|
|
}
|
|
|
|
(*it).second.edge_point *= 1.f/((*it).second.ref+2.f);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bad_cnt) {
|
|
|
|
// Report the number of bad edges. bad edges are referenced by less than two
|
|
|
|
// faces in the mesh. They occur at outer model boundaries in non-closed
|
|
|
|
// shapes.
|
2020-05-15 11:59:48 +00:00
|
|
|
ASSIMP_LOG_VERBOSE_DEBUG_F("Catmull-Clark Subdivider: got ", bad_cnt, " bad edges touching only one face (totally ",
|
2018-04-26 12:10:18 +00:00
|
|
|
static_cast<unsigned int>(edges.size()), " edges). ");
|
2015-05-19 03:57:13 +00:00
|
|
|
}}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
// 4. Compute a vertex-face adjacency table. We can't reuse the code
|
|
|
|
// from VertexTriangleAdjacency because we need the table for multiple
|
|
|
|
// meshes and out vertex indices need to be mapped to distinct values
|
|
|
|
// first.
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
UIntVector faceadjac(nfacesout), cntadjfac(maptbl.size(),0), ofsadjvec(maptbl.size()+1,0); {
|
|
|
|
for (size_t t = 0; t < nmesh; ++t) {
|
|
|
|
const aiMesh* const minp = smesh[t];
|
|
|
|
for (unsigned int i = 0; i < minp->mNumFaces; ++i) {
|
|
|
|
|
|
|
|
const aiFace& f = minp->mFaces[i];
|
|
|
|
for (unsigned int n = 0; n < f.mNumIndices; ++n) {
|
|
|
|
++cntadjfac[maptbl[FLATTEN_VERTEX_IDX(t,f.mIndices[n])]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
unsigned int cur = 0;
|
|
|
|
for (size_t i = 0; i < cntadjfac.size(); ++i) {
|
|
|
|
ofsadjvec[i+1] = cur;
|
|
|
|
cur += cntadjfac[i];
|
|
|
|
}
|
|
|
|
for (size_t t = 0; t < nmesh; ++t) {
|
|
|
|
const aiMesh* const minp = smesh[t];
|
|
|
|
for (unsigned int i = 0; i < minp->mNumFaces; ++i) {
|
|
|
|
|
|
|
|
const aiFace& f = minp->mFaces[i];
|
|
|
|
for (unsigned int n = 0; n < f.mNumIndices; ++n) {
|
|
|
|
faceadjac[ofsadjvec[1+maptbl[FLATTEN_VERTEX_IDX(t,f.mIndices[n])]]++] = FLATTEN_FACE_IDX(t,i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check the other way round for consistency
|
2015-05-19 03:48:29 +00:00
|
|
|
#ifdef ASSIMP_BUILD_DEBUG
|
|
|
|
|
2015-05-19 03:57:13 +00:00
|
|
|
for (size_t t = 0; t < ofsadjvec.size()-1; ++t) {
|
|
|
|
for (unsigned int m = 0; m < cntadjfac[t]; ++m) {
|
|
|
|
const unsigned int fidx = faceadjac[ofsadjvec[t]+m];
|
|
|
|
ai_assert(fidx < totfaces);
|
|
|
|
for (size_t n = 1; n < nmesh; ++n) {
|
|
|
|
|
|
|
|
if (moffsets[n].first > fidx) {
|
|
|
|
const aiMesh* msh = smesh[--n];
|
|
|
|
const aiFace& f = msh->mFaces[fidx-moffsets[n].first];
|
|
|
|
|
|
|
|
bool haveit = false;
|
|
|
|
for (unsigned int i = 0; i < f.mNumIndices; ++i) {
|
|
|
|
if (maptbl[FLATTEN_VERTEX_IDX(n,f.mIndices[i])]==(unsigned int)t) {
|
2015-08-30 11:48:00 +00:00
|
|
|
haveit = true;
|
|
|
|
break;
|
2015-05-19 03:57:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
ai_assert(haveit);
|
2015-08-30 11:48:00 +00:00
|
|
|
if (!haveit) {
|
2020-05-15 11:59:48 +00:00
|
|
|
ASSIMP_LOG_VERBOSE_DEBUG("Catmull-Clark Subdivider: Index not used");
|
2015-08-30 11:48:00 +00:00
|
|
|
}
|
2015-05-19 03:57:13 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-05-19 03:48:29 +00:00
|
|
|
|
|
|
|
#endif
|
2015-05-19 03:57:13 +00:00
|
|
|
}
|
2015-05-19 03:48:29 +00:00
|
|
|
|
|
|
|
#define GET_ADJACENT_FACES_AND_CNT(vidx,fstartout,numout) \
|
2015-05-19 03:57:13 +00:00
|
|
|
fstartout = &faceadjac[ofsadjvec[vidx]], numout = cntadjfac[vidx]
|
|
|
|
|
|
|
|
typedef std::pair<bool,Vertex> TouchedOVertex;
|
|
|
|
std::vector<TouchedOVertex > new_points(num_unique,TouchedOVertex(false,Vertex()));
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
// 5. Spawn a quad from each face point to the corresponding edge points
|
|
|
|
// the original points being the fourth quad points.
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
for (size_t t = 0; t < nmesh; ++t) {
|
|
|
|
const aiMesh* const minp = smesh[t];
|
|
|
|
aiMesh* const mout = out[t] = new aiMesh();
|
|
|
|
|
|
|
|
for (unsigned int a = 0; a < minp->mNumFaces; ++a) {
|
|
|
|
mout->mNumFaces += minp->mFaces[a].mNumIndices;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We need random access to the old face buffer, so reuse is not possible.
|
|
|
|
mout->mFaces = new aiFace[mout->mNumFaces];
|
|
|
|
|
|
|
|
mout->mNumVertices = mout->mNumFaces*4;
|
|
|
|
mout->mVertices = new aiVector3D[mout->mNumVertices];
|
|
|
|
|
|
|
|
// quads only, keep material index
|
|
|
|
mout->mPrimitiveTypes = aiPrimitiveType_POLYGON;
|
|
|
|
mout->mMaterialIndex = minp->mMaterialIndex;
|
|
|
|
|
|
|
|
if (minp->HasNormals()) {
|
|
|
|
mout->mNormals = new aiVector3D[mout->mNumVertices];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (minp->HasTangentsAndBitangents()) {
|
|
|
|
mout->mTangents = new aiVector3D[mout->mNumVertices];
|
|
|
|
mout->mBitangents = new aiVector3D[mout->mNumVertices];
|
|
|
|
}
|
|
|
|
|
|
|
|
for(unsigned int i = 0; minp->HasTextureCoords(i); ++i) {
|
|
|
|
mout->mTextureCoords[i] = new aiVector3D[mout->mNumVertices];
|
|
|
|
mout->mNumUVComponents[i] = minp->mNumUVComponents[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
for(unsigned int i = 0; minp->HasVertexColors(i); ++i) {
|
|
|
|
mout->mColors[i] = new aiColor4D[mout->mNumVertices];
|
|
|
|
}
|
|
|
|
|
|
|
|
mout->mNumVertices = mout->mNumFaces<<2u;
|
|
|
|
for (unsigned int i = 0, v = 0, n = 0; i < minp->mNumFaces;++i) {
|
|
|
|
|
|
|
|
const aiFace& face = minp->mFaces[i];
|
|
|
|
for (unsigned int a = 0; a < face.mNumIndices;++a) {
|
|
|
|
|
|
|
|
// Get a clean new face.
|
|
|
|
aiFace& faceOut = mout->mFaces[n++];
|
|
|
|
faceOut.mIndices = new unsigned int [faceOut.mNumIndices = 4];
|
|
|
|
|
|
|
|
// Spawn a new quadrilateral (ccw winding) for this original point between:
|
|
|
|
// a) face centroid
|
|
|
|
centroids[FLATTEN_FACE_IDX(t,i)].SortBack(mout,faceOut.mIndices[0]=v++);
|
|
|
|
|
|
|
|
// b) adjacent edge on the left, seen from the centroid
|
|
|
|
const Edge& e0 = edges[MAKE_EDGE_HASH(maptbl[FLATTEN_VERTEX_IDX(t,face.mIndices[a])],
|
|
|
|
maptbl[FLATTEN_VERTEX_IDX(t,face.mIndices[a==face.mNumIndices-1?0:a+1])
|
|
|
|
])]; // fixme: replace with mod face.mNumIndices?
|
|
|
|
|
|
|
|
// c) adjacent edge on the right, seen from the centroid
|
|
|
|
const Edge& e1 = edges[MAKE_EDGE_HASH(maptbl[FLATTEN_VERTEX_IDX(t,face.mIndices[a])],
|
|
|
|
maptbl[FLATTEN_VERTEX_IDX(t,face.mIndices[!a?face.mNumIndices-1:a-1])
|
|
|
|
])]; // fixme: replace with mod face.mNumIndices?
|
|
|
|
|
|
|
|
e0.edge_point.SortBack(mout,faceOut.mIndices[3]=v++);
|
|
|
|
e1.edge_point.SortBack(mout,faceOut.mIndices[1]=v++);
|
|
|
|
|
|
|
|
// d= original point P with distinct index i
|
|
|
|
// F := 0
|
|
|
|
// R := 0
|
|
|
|
// n := 0
|
|
|
|
// for each face f containing i
|
|
|
|
// F := F+ centroid of f
|
|
|
|
// R := R+ midpoint of edge of f from i to i+1
|
|
|
|
// n := n+1
|
|
|
|
//
|
|
|
|
// (F+2R+(n-3)P)/n
|
|
|
|
const unsigned int org = maptbl[FLATTEN_VERTEX_IDX(t,face.mIndices[a])];
|
|
|
|
TouchedOVertex& ov = new_points[org];
|
|
|
|
|
|
|
|
if (!ov.first) {
|
|
|
|
ov.first = true;
|
|
|
|
|
|
|
|
const unsigned int* adj; unsigned int cnt;
|
|
|
|
GET_ADJACENT_FACES_AND_CNT(org,adj,cnt);
|
|
|
|
|
|
|
|
if (cnt < 3) {
|
|
|
|
ov.second = Vertex(minp,face.mIndices[a]);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
|
|
|
|
Vertex F,R;
|
|
|
|
for (unsigned int o = 0; o < cnt; ++o) {
|
|
|
|
ai_assert(adj[o] < totfaces);
|
|
|
|
F += centroids[adj[o]];
|
|
|
|
|
|
|
|
// adj[0] is a global face index - search the face in the mesh list
|
|
|
|
const aiMesh* mp = NULL;
|
|
|
|
size_t nidx;
|
|
|
|
|
|
|
|
if (adj[o] < moffsets[0].first) {
|
|
|
|
mp = smesh[nidx=0];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
for (nidx = 1; nidx<= nmesh; ++nidx) {
|
|
|
|
if (nidx == nmesh ||moffsets[nidx].first > adj[o]) {
|
|
|
|
mp = smesh[--nidx];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ai_assert(adj[o]-moffsets[nidx].first < mp->mNumFaces);
|
|
|
|
const aiFace& f = mp->mFaces[adj[o]-moffsets[nidx].first];
|
|
|
|
bool haveit = false;
|
|
|
|
|
|
|
|
// find our original point in the face
|
|
|
|
for (unsigned int m = 0; m < f.mNumIndices; ++m) {
|
|
|
|
if (maptbl[FLATTEN_VERTEX_IDX(nidx,f.mIndices[m])] == org) {
|
|
|
|
|
|
|
|
// add *both* edges. this way, we can be sure that we add
|
|
|
|
// *all* adjacent edges to R. In a closed shape, every
|
|
|
|
// edge is added twice - so we simply leave out the
|
|
|
|
// factor 2.f in the amove formula and get the right
|
|
|
|
// result.
|
|
|
|
|
|
|
|
const Edge& c0 = edges[MAKE_EDGE_HASH(org,maptbl[FLATTEN_VERTEX_IDX(
|
|
|
|
nidx,f.mIndices[!m?f.mNumIndices-1:m-1])])];
|
|
|
|
// fixme: replace with mod face.mNumIndices?
|
|
|
|
|
|
|
|
const Edge& c1 = edges[MAKE_EDGE_HASH(org,maptbl[FLATTEN_VERTEX_IDX(
|
|
|
|
nidx,f.mIndices[m==f.mNumIndices-1?0:m+1])])];
|
|
|
|
// fixme: replace with mod face.mNumIndices?
|
|
|
|
R += c0.midpoint+c1.midpoint;
|
|
|
|
|
|
|
|
haveit = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// this invariant *must* hold if the vertex-to-face adjacency table is valid
|
|
|
|
ai_assert(haveit);
|
2016-12-31 15:07:11 +00:00
|
|
|
if ( !haveit ) {
|
2018-04-20 14:23:24 +00:00
|
|
|
ASSIMP_LOG_WARN( "OBJ: no name for material library specified." );
|
2016-12-31 15:07:11 +00:00
|
|
|
}
|
2015-05-19 03:57:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const float div = static_cast<float>(cnt), divsq = 1.f/(div*div);
|
|
|
|
ov.second = Vertex(minp,face.mIndices[a])*((div-3.f) / div) + R*divsq + F*divsq;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ov.second.SortBack(mout,faceOut.mIndices[2]=v++);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // end of scope for edges, freeing its memory
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
// 7. Apply the next subdivision step.
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
if (num != 1) {
|
|
|
|
std::vector<aiMesh*> tmp(nmesh);
|
|
|
|
InternSubdivide (out,nmesh,&tmp.front(),num-1);
|
|
|
|
for (size_t i = 0; i < nmesh; ++i) {
|
|
|
|
delete out[i];
|
|
|
|
out[i] = tmp[i];
|
|
|
|
}
|
|
|
|
}
|
2015-05-19 03:48:29 +00:00
|
|
|
}
|