Support concave and/or n-vertex faces#85
Conversation
fbcd28b to
50d26f4
Compare
|
CC @erich666 |
|
@lilleyse and I discussed the broken tests -- looks like they differ only in the long generated string. Not sure what causes this? The test objs look fine though. Also, my changes introduce quite a bit more computation per face. I'm not familiar with the sorts of usage this converter gets, but if really large models are common (and the n-gon / concave face cases are uncommon), it might be worth making this an option and using a simple face method for triangulation the default case. |
|
Performance will definitely be important here. Instead of relying on user input, can't we do a quick test to see if this is needed or not? |
|
Right, I suspect 90% of the time it's a quick out, "there are three vertices." Another 9.9% of the time it'll be a quad, and maybe there's a quick out for those, but I don't know one off-hand. I'd use: Graphics Gems IV: Schorn, Peter, and Fisher, Frederick, Testing the Convexity of a Polygon, p. 7-15, code: p. 11-15, convex_test/. Code is here: There might be something newer somewhere, but this is the one I know, and I recall it's pretty optimal (it can also identify degenerate polygons, which you can simply discard if you know they're part of a surface). |
|
Thanks for the reference, @erich666! |
9697980 to
259bdd5
Compare
|
Hey @lilleyse , would you like to start looking at this? I'm not sure what to do about winding ordering. |
259bdd5 to
2396899
Compare
58823f6 to
2105e7b
Compare
|
Ok, I added winding order sanitization for when normals are provided. I haven't added degenerate polygon sanitization, but this PR has already gotten a lot bigger than I originally intended, so perhaps that's best left to a follow up PR. |
|
Bump @lilleyse |
1 similar comment
|
Bump @lilleyse |
|
@rahwang The reason the tests just look like really long string differences is because jasmine is doing a comparison of 2 gltf objects, this test fails because the output has changed. Jasmine doesn't know how to do anything other than stringify the objects as part of the error message (however this is enough for us to go on.) If you diff the two strings provided by the error message, what you find is that the glTF buffers are different between master and this branch (since they are base64 encoded it's hard to say at a glance exactly what is different). Long story short, (too late) if you convert It's possible that your changes are still correct and that the test is too fragile and needs to be updated, but the important next step for you here is to convert box.obj both in master and in this branch and determine exactly what is different and why and which one is more correct. (I would also recommend you merge master into this branch before doing that just in case). Hope that helps! |
|
Thank @mramato! I'll try to hunt down the differences once I get another chance to talk to Sean -- we were also considering moving the winding order correction into gltf pipeline. |
| parseFaceLine(line, true, true); | ||
| } else if (facePattern4.test(line) || (continuedLine && facePattern8.test(line))) { // format "v//n v//n v//n ..." | ||
| parseFaceLine(line, false, true); | ||
| } |
There was a problem hiding this comment.
After looking at the multi-line parsing a bit, I think it could be simplified by buffering the string rather than buffering the parsed data. The face parsing code should take a single string regardless of whether it originally spanned multiple lines or not.
Then with some modifications to the first four regexes it should be possible to capture all the attributes like before, with addFace just handling them in a more generic fashion.
There was a problem hiding this comment.
Ah, that would definitely be neater -- I'll modify this to use line buffering now.
Originally those tests acted as "integration" tests, but they are too fragile and should probably be removed. |
3da0063 to
2706b95
Compare
|
Thanks for the suggestion, @lilleyse ! I reorganized the line buffering. This is ready for further review. |
| // facePosition : indices into position array | ||
| // faceUvs : indices into uv array | ||
| // faceNormals : indices into normal array | ||
| function parseFaceLine(line, hasUvs, hasNormals) { |
There was a problem hiding this comment.
It might be best to remove this function entirely and put the logic back inside each of the cases within parseLine. That way we can avoid separate areas having similar if-else chains.
There was a problem hiding this comment.
Along with this, remove faceSpacePattern and faceSpaceOrSlashPattern and capture attributes in the four main regex's instead.
There was a problem hiding this comment.
After reading up on this more, it may not be possible to capture everything since it's looking for n occurrences...
There was a problem hiding this comment.
Yeah, as far as I can tell, you have to just iterate through the string and grab attributes manually if there are n of them -- capture groups don't work with n elements.
As for getting rid of parseFaceLine, this will create a bunch of repeated code in each of the face cases --is that ok?
There was a problem hiding this comment.
I haven't been following this thread too closely, but a single regex match can capture all occurrences of a given pattern and will be way faster than manual string manipulation (which should always be avoided if possible). See https://stackoverflow.com/a/14707522/3191039
| result[10], result[11], undefined, result[12] | ||
| ); | ||
| } else { // face line or invalid line | ||
|
|
|
|
||
| var facePositions = []; | ||
| var faceUvs = []; | ||
| var faceNormals = []; |
There was a problem hiding this comment.
These are good to have but will allocate for every face line that is parsed. Instead put these at global scope and call facePositions.length = 0 for each at the end of the parsing.
To clean up some of the code here and in addFace don't set these to undefined if the face doesn't use that attribute, just pass the arrays as-is and check the length if required. It's fine to index out-of-bounds like below since the result will just be undefined.
addVertex(vertices[0], positions[0], uvs[0], normals[0]);
| } else if (facePattern4.test(lineBuffer)) { // format "v//n v//n v//n ..." | ||
| parseFaceLine(line, false, true); | ||
| } | ||
| lineBuffer = ""; |
There was a problem hiding this comment.
Style: use single quotes.
| var mtlPaths = []; | ||
|
|
||
| // Buffers for face data that spans multiple lines | ||
| var lineBuffer = ""; |
There was a problem hiding this comment.
Style: use single quotes.
|
|
||
| var vertexPattern = /v( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // v float float float | ||
| var normalPattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // vn float float float | ||
| var uvPattern = /vt( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // vt float float |
There was a problem hiding this comment.
Can these three also benefit from the {3} syntax like below?
There was a problem hiding this comment.
I don't think they do, because unlike the face line parsing, there are a set number of them them, each with a capture group. The face lines are different because there are n items. The {3,} just specifies at least 3.
| } | ||
|
|
||
| var scratchNormal = new Cartesian3(); | ||
| function addFace(vertices, positions, uvs, normals) { |
There was a problem hiding this comment.
How easy would it be to move winding order correct / triangulation code into a separate file?
There was a problem hiding this comment.
Definitely doable, but the file logic will be pretty tangled though, because it will essentially be all the logic from addFace, which uses addVertex, the global normals / position buffers and the face position and normal buffers. Don't know if it's worth it?
|
Ok @lilleyse , ready for another look! |
6b5ebb7 to
f526e9b
Compare
lilleyse
left a comment
There was a problem hiding this comment.
I'm really late here, but the approach is a lot cleaner now.
| var faceVertices = []; // names of vertices for caching | ||
| var facePositions = []; // indices into position array | ||
| var faceUvs = []; // indices into uv array | ||
| var faceNormals = []; // indices into normal array |
There was a problem hiding this comment.
The inline comments probably aren't needed.
| primitive.material = getName(name); | ||
| } | ||
|
|
||
| var intPoint = new Cartesian3(); |
There was a problem hiding this comment.
Rename to scratchIntersectionPoint. Also prepend scratch to the other variables.
| var intPoint = new Cartesian3(); | ||
| var xAxis = Cesium.Cartesian3.UNIT_X.clone(); | ||
| var yAxis = Cesium.Cartesian3.UNIT_Y.clone(); | ||
| var zAxis = Cesium.Cartesian3.UNIT_Z.clone(); |
There was a problem hiding this comment.
Do these need be cloned from UNIT_X and others? I think they can just be new Cesium.Cartesian3().
| var ray = new Ray(); | ||
| var plane = new Plane(Cesium.Cartesian3.UNIT_X, 0); | ||
| function projectTo2D(positions) { | ||
| var positions2D = new Array(positions.length); |
There was a problem hiding this comment.
It would be best to use a scratch array for positions2D too. There should also be some bank of scratch Cartesian2 that can be used in place of the allocations below. The same applies to the cartesians that are allocated for positions3D.
| var normal = new Cartesian3(); | ||
| var ray = new Ray(); | ||
| var plane = new Plane(Cesium.Cartesian3.UNIT_X, 0); | ||
| function projectTo2D(positions) { |
There was a problem hiding this comment.
Move this function down below a bit, maybe right above get3DPoint.
aff89ff to
563d524
Compare
|
Thanks for reviewing, @lilleyse ! Did requested cleanup edits. |
|
Thanks for catching that @lilleyse ! It was a small bug with some scratch cart3s. Fixed. The converted model now looks like this: |
|
Thanks a lot @rahwang! |



Fixes #79 and #55.
Modify face-loading logic to support faces that are n-gons that may be concave.
Also replacing the box-triangles obj test file, because it didn't actually have triangular faces.
Can you review, @lilleyse ?