Skip to content
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

Merged
merged 11 commits into from Aug 9, 2017

Conversation

rahwang
Copy link
Contributor

@rahwang rahwang commented Jun 14, 2017

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 ?

@pjcozzi
Copy link
Contributor

pjcozzi commented Jun 14, 2017

CC @erich666

@rahwang
Copy link
Contributor Author

rahwang commented Jun 14, 2017

@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.

@pjcozzi
Copy link
Contributor

pjcozzi commented Jun 14, 2017

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?

@erich666
Copy link

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:
http://www.realtimerendering.com/resources/GraphicsGems/gemsiv/convex_test/ - two versions, and I assume _opt.c is newer and better.

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).

@pjcozzi
Copy link
Contributor

pjcozzi commented Jun 14, 2017

Thanks for the reference, @erich666!

@rahwang rahwang force-pushed the more-vertices-faces branch 2 times, most recently from 9697980 to 259bdd5 Compare June 15, 2017 22:58
@rahwang
Copy link
Contributor Author

rahwang commented Jun 15, 2017

Hey @lilleyse , would you like to start looking at this? I'm not sure what to do about winding ordering.

@rahwang
Copy link
Contributor Author

rahwang commented Jun 16, 2017

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.

@lilleyse

@pjcozzi
Copy link
Contributor

pjcozzi commented Jun 19, 2017

Bump @lilleyse

1 similar comment
@pjcozzi
Copy link
Contributor

pjcozzi commented Jun 21, 2017

Bump @lilleyse

@mramato
Copy link
Contributor

mramato commented Jun 23, 2017

@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 specs/data/box/box.obj in this branch, you end up with a different glTF than in master.

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!

@rahwang
Copy link
Contributor Author

rahwang commented Jun 26, 2017

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);
}
Copy link
Contributor

@lilleyse lilleyse Jun 26, 2017

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.

Copy link
Contributor Author

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.

@lilleyse
Copy link
Contributor

It's possible that your changes are still correct and that the test is too fragile and needs to be updated

Originally those tests acted as "integration" tests, but they are too fragile and should probably be removed.

@rahwang
Copy link
Contributor Author

rahwang commented Jun 26, 2017

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) {
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor

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...

Copy link
Contributor Author

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?

Copy link
Contributor

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

Copy link
Contributor

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 = [];
Copy link
Contributor

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]);

Copy link
Contributor Author

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 = "";
Copy link
Contributor

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 = "";
Copy link
Contributor

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
Copy link
Contributor

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?

Copy link
Contributor Author

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) {
Copy link
Contributor

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?

Copy link
Contributor Author

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?

@rahwang
Copy link
Contributor Author

rahwang commented Jun 27, 2017

Ok @lilleyse , ready for another look!

Copy link
Contributor

@lilleyse lilleyse left a 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
Copy link
Contributor

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();
Copy link
Contributor

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();
Copy link
Contributor

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);
Copy link
Contributor

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) {
Copy link
Contributor

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.

@rahwang
Copy link
Contributor Author

rahwang commented Jul 27, 2017

Thanks for reviewing, @lilleyse ! Did requested cleanup edits.

@lilleyse
Copy link
Contributor

lilleyse commented Aug 8, 2017

@rahwang could you test out the model in #97? The triangulation is working but I think the winding order may be producing inconsistent results for 4-sided polygons.

android

android-bfc

In the second image I removed back face culling from the gltf.

@rahwang
Copy link
Contributor Author

rahwang commented Aug 8, 2017

Thanks for catching that @lilleyse ! It was a small bug with some scratch cart3s. Fixed. The converted model now looks like this:

screen shot 2017-08-08 at 11 58 03 am

@03lafaye
Copy link

03lafaye commented Aug 9, 2017

Awesome! Thanks!

myjobisdone

@lilleyse
Copy link
Contributor

lilleyse commented Aug 9, 2017

Thanks a lot @rahwang!

@lilleyse lilleyse merged commit b20df62 into CesiumGS:master Aug 9, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants