-
Notifications
You must be signed in to change notification settings - Fork 951
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow 3d triangulation #1691
base: master
Are you sure you want to change the base?
Allow 3d triangulation #1691
Conversation
2780e38
to
975ca08
Compare
@@ -200,7 +200,7 @@ class TriMesh : public geom::Source { | |||
size_t getNumIndices() const override { return mIndices.size(); } | |||
//! Returns the total number of triangles contained by the TriMesh. | |||
size_t getNumTriangles() const { return mIndices.size() / 3; } | |||
//! Returns the total number of indices contained by the TriMesh. | |||
//! Returns the total number of verticies contained by the TriMesh. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was referring to these docs and this seemed like a bit of copy paste from a couple lines above.
include/cinder/Triangulate.h
Outdated
//! Performs the tesselation, returning a TriMesh2d | ||
TriMeshRef createMesh( Winding winding = WINDING_ODD ); | ||
TriMeshRef createMesh( Winding winding = WINDING_ODD, u_int8_t positionDims = 2 ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a fan of this interface but couldn't think of anything better.
src/cinder/Triangulate.cpp
Outdated
if( positionDims == 2 ) { | ||
result.appendPositions( (vec2*)tessGetVertices( mTess.get() ), tessGetVertexCount( mTess.get() ) ); | ||
} else { | ||
result.appendPositions( (vec3*)tessGetVertices( mTess.get() ), tessGetVertexCount( mTess.get() ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd love suggestions on better ways to make this type conversion conditional.
Will this PR allow people to add both 2D and 3D vertices to the same In that case it would be better to have a |
It seems like libtess2 uses 3 dimensions internally and leaves the z at 0 if you don't provide a value but I think it could be clearer to have specialized classes for each. I'll take a swing at that interface and see how it looks. |
I took a swing at putting the 3d version in it's own class. I'm not sure if there's a good way to share code between the two classes. Since this is really just a proof of concept I set it up as a |
How about converting into a templated class? template<typename T>
class Triangulator {
};
typedef Triangulator<vec2> Triangulator2;
typedef Triangulator<vec3> Triangulator3; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know the code is copied from the existing class, but the allocate function seems buggy to me. However, the fact that people apparently used it successfully in the past might mean that I'm wrong.
src/cinder/Triangulate.cpp
Outdated
ma.memalloc = stdAlloc; | ||
ma.memfree = stdFree; | ||
ma.userData = (void*)&mAllocated; | ||
ma.extraVertices = 2560; // realloc not provided, allow 256 extra vertices. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment doesn't fit the code (256 <-> 2560 ) - is the 0 there deliberate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah been noticing some other weird behavior since that structure has an outOfMemory
member that isn't explicitly initialized to 0. So sporadically it seems to fail once it reuses memory the OS hasn't zeroed out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or rather, the TESStesselator's outOfMemory value wasn't being initialized. It seems like we could switch the malloc to a calloc in our stdAlloc.
But to your original point I'm not sure why that number changed or if it was accidental.
src/cinder/Triangulate.cpp
Outdated
ma.userData = (void*)&mAllocated; | ||
ma.extraVertices = 2560; // realloc not provided, allow 256 extra vertices. | ||
|
||
mTess = shared_ptr<TESStesselator>( tessNewTess( &ma ), tessDeleteTess ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems a bit fishy to me. I don't know how exactly the tess library works, but you are passing the address of a local variable (ma
) to a function, that will take that address and store it in a heap allocated TESStesselator
object which will then be used long after the local object is gone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah agreed it seem suspect. it looks like tessNewTess
stores a reference to it in
the mTess we get back. i'm not sure why it doesn't cause more obvious problems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MikeGitb what seems like a sane way to handle the TESSalloc? it seems like it should be an instance variable but then you'd need to include tesselator.h
in Triangulate.h
. so pointer or shared_ptr?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@drewish: If backwards compatibility was not an issue, I'd wrap the mTess
pointer, the allocator and the used tess...
functions and in particular the allocate
function into a - forward declared - class and store a single std::unique_ptr
. As things stand just creating a member variable for the allocator as you suggested seems to be the best solution.
However, as it is a private/protected variable that isn't shared with anything, I'd not use a shared_ptr
but a unique_ptr
.
src/cinder/Triangulate.cpp
Outdated
{ | ||
TriMesh result( TriMesh::Format().positions( 3 ) ); | ||
|
||
tessTesselate( mTess.get(), (int)winding, TESS_POLYGONS, 3, 3, 0 ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This returns a 1 for success but we're not checking it leading to very strange results when you hit the outOfMemory bug I mentioned in the other comment.
@vinjn yeah that was what i'd looked at initially but i wasn't clear how you'd get a scalar |
int getVecDim(vec3) { return 3;}
int getVecDim(vec2) { return 2;} or using type traits like |
I'm thinking this might be hard to generalize for random 3d input. I've been getting really strange behavior with multiple contours. Adding a second contour causes it to return no output. I think this is due to the way libtess builds the arrangement by projecting everything onto a single sweep plane. Duplicated points on the plan seem to clear the output. It looks like you can influence the choice of plane by passing in a normal. So I think the only way it would reliably work in 3d would be to either require a normal be provided when calculating the mesh or just limit it to a single input contour. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm beginning to think that Triangulate needs a major overhaul. found two more bugs.
|
||
tessTesselate( mTess.get(), (int)winding, TESS_POLYGONS, 3, 3, 0 ); | ||
result->appendPositions( (vec3*)tessGetVertices( mTess.get() ), tessGetVertexCount( mTess.get() ) ); | ||
result->appendIndices( (uint32_t*)( tessGetElements( mTess.get() ) ), tessGetElementCount( mTess.get() ) * 3 ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that cast from const int*
(returned by tessGetElements) to uint32_t*
is actually undefined bahavior (illegal)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah there's a few of these conversion that seemed sketchy but short of changing the libtess I wasn't sure what to do about that. the lib seems to use signed ints for a lot of things that seemed best as unsigned.
src/cinder/Triangulate.cpp
Outdated
void Triangulator3d::addPolyLine( const vec3 *points, size_t numPoints ) | ||
{ | ||
if( numPoints > 0 ) | ||
tessAddContour( mTess.get(), 3, &points, sizeof(vec3), (int)numPoints ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the &
in front of points
correct here? I don't think we want a pointer to a pointer here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope it's a bug. Added a test case to drive this code path and it failed until I removed the &
.
src/cinder/Triangulate.cpp
Outdated
|
||
mTess = shared_ptr<TESStesselator>( tessNewTess( &ma ), tessDeleteTess ); | ||
|
||
mAlloc = shared_ptr<TESSalloc>( new TESSalloc() ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MikeGitb I tried doing a shared_ptr on this but got a rather cryptic error:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/memory:2523:27: Invalid application of 'sizeof' to an incomplete type 'TESSalloc'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume the problem is that Triangulate has an implicitly defined destructor. That means that the compiler tries to generate the destructor in every translation unit that includes the header file. However, in most translation units, it doesn't have access to the Definition of TESSalloc which it apparently needs for the destructor of mAlloc.
Try defining an empty destructor in Triangulate.cpp
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you really want to use a shared_ptr instead of a unique_ptr may I suggest using std::make_shared
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry that was a typo, i meant to write unique_ptr. i agree that makes more sense so i'll try adding a destructor and see if that helps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At least after adding the destructor I'm getting an error that's understandable:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/memory:2523:27: Invalid application of 'sizeof' to an incomplete type 'TESSalloc'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't that the same message as you got before?
src/cinder/Triangulate.cpp
Outdated
void Triangulator::addPolyLine( const PolyLine3f &polyLine ) | ||
{ | ||
if( polyLine.size() > 0 ) | ||
tessAddContour( mTess.get(), 3, &polyLine.getPoints()[0], sizeof(vec3), (int)polyLine.size() ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason not to just do polyLine.getPoints().data()
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, that also seems more logical to me (probably pre c++11 code when vector didn't have the data()
member function). I'd usually also not write sizeof(vec3)
, but sizeof(*polyLine.getPoints().data())
as this makes the code more generic and less prone to refactoring erors (but admittedly also more ugly)
@@ -482,6 +482,7 @@ TESStesselator* tessNewTess( TESSalloc* alloc ) | |||
if ( tess == NULL ) { | |||
return 0; /* out of memory */ | |||
} | |||
tess->outOfMemory = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since there was already one other edit to this lib this seemed like the cleanest way to make sure this value was initialized.
I went ahead and folded this back into one class. The only difference is flow is really in the mesh outputting so I just added a parallel set of calc/createMesh3d functions. The 3d version takes a normal and I think in the docs we just need to make it clear that you only want to tesselate things on the same plane unless you know what you're doing. |
a2b6128
to
8d14093
Compare
I was wrong btw.: The cast from |
Since over on #1698 it sounded like PolyLine3 might not be around long I switched this interface to just use |
eb1430b
to
2ffc84e
Compare
9365852
to
e53ef4a
Compare
|
||
tessTesselate( mTess.get(), (int)winding, TESS_POLYGONS, 3, 2, 0 ); | ||
|
||
float normal[3] = { 0, 0, 1 }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I'd started passing this in to avoid computing it, but I need to test it out. I think the old behavior would flip the normal to use a (0,0,-1) if the winding order was backwards. I need to add test cases for that.
e53ef4a
to
514dedd
Compare
2aa4f3e
to
8bcb70c
Compare
- Provide a normal for 2d tessleation to avoid computing one - Fix bug when adding array of vec2 - Adding some unit tests
Any reason this didn't get merged? |
Not that I'm aware of. Happy to rebase it or make any changes that are necessary. |
Sorry for the unreasonable delay on this. Two quick questions as my head is no longer in this code unfortunately. First, should we consider moving to the latest here? https://github.com/memononen/libtess2 And second, does the tesselator do the right thing if I supply a mixture of 2D and 3D contours as input? |
I needed to do some basic 3d triangulation and noticed that libtess2 supports it but the Triangulator wrapper did not. It wasn't too hard to hook it up but the mesh output is wonky. I've added some really basic unit tests to demonstrate what it's doing.