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
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. |
lib/loadObj.js
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
lib/loadObj.js
Outdated
// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Along with this, remove faceSpacePattern
and faceSpaceOrSlashPattern
and capture attributes in the four main regex's instead.
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
lib/loadObj.js
Outdated
result[10], result[11], undefined, result[12] | ||
); | ||
} else { // face line or invalid line | ||
|
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.
Remove empty line.
lib/loadObj.js
Outdated
|
||
var facePositions = []; | ||
var faceUvs = []; | ||
var faceNormals = []; |
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.
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]);
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.
Good idea!
lib/loadObj.js
Outdated
} else if (facePattern4.test(lineBuffer)) { // format "v//n v//n v//n ..." | ||
parseFaceLine(line, false, true); | ||
} | ||
lineBuffer = ""; |
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.
Style: use single quotes.
lib/loadObj.js
Outdated
@@ -91,6 +101,9 @@ function loadObj(objPath, options) { | |||
// All mtl paths seen in the obj | |||
var mtlPaths = []; | |||
|
|||
// Buffers for face data that spans multiple lines | |||
var lineBuffer = ""; |
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.
Style: use single quotes.
lib/loadObj.js
Outdated
|
||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can these three also benefit from the {3} syntax like below?
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 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.
lib/loadObj.js
Outdated
} | ||
} | ||
|
||
var scratchNormal = new Cartesian3(); | ||
function addFace(vertices, positions, uvs, normals) { |
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.
How easy would it be to move winding order correct / triangulation code into a separate file?
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.
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
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 really late here, but the approach is a lot cleaner now.
lib/loadObj.js
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The inline comments probably aren't needed.
lib/loadObj.js
Outdated
@@ -135,6 +152,94 @@ function loadObj(objPath, options) { | |||
primitive.material = getName(name); | |||
} | |||
|
|||
var intPoint = new Cartesian3(); |
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.
Rename to scratchIntersectionPoint
. Also prepend scratch
to the other variables.
lib/loadObj.js
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do these need be cloned from UNIT_X
and others? I think they can just be new Cesium.Cartesian3()
.
lib/loadObj.js
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
.
lib/loadObj.js
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 ?