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

GLTFExporter: Prevent exporting empty geometry #13681

Merged
merged 9 commits into from
Mar 27, 2018
Merged

Conversation

fernandojsg
Copy link
Collaborator

@fernandojsg fernandojsg commented Mar 25, 2018

I've just found a bug when exporting empty buffer geometries. eg in a-painter I create a buffer geometry for each kind of material but maybe I don't use one of them so drawRange will be 0. If you try to export it it will generate the following glTF:

{
  "asset": {
    "version": "2.0",
    "generator": "THREE.GLTFExporter"
  },
  "scenes": [
    {
      "nodes": [
        0
      ],
      "name": "AuxScene"
    }
  ],
  "scene": 0,
  "nodes": [
    {
      "name": "Custom buffered empty (drawrange)",
      "mesh": 0
    }
  ],
  "bufferViews": [
    {
      "buffer": 0,
      "byteOffset": 0,
      "byteLength": 0,
      "target": 34962,
      "byteStride": 12
    },
    {
      "buffer": 0,
      "byteOffset": 0,
      "byteLength": 0,
      "target": 34962,
      "byteStride": 12
    }
  ],
  "buffers": [
    {
      "byteLength": 0,
      "uri": "data:"
    }
  ],
  "accessors": [
    {
      "bufferView": 0,
      "componentType": 5126,
      "count": 0,
      "max": [
        null,
        null,
        null
      ],
      "min": [
        null,
        null,
        null
      ],
      "type": "VEC3"
    },
    {
      "bufferView": 1,
      "componentType": 5126,
      "count": 0,
      "max": [
        null,
        null,
        null
      ],
      "min": [
        null,
        null,
        null
      ],
      "type": "VEC3"
    }
  ],
  "materials": [
    {
      "pbrMetallicRoughness": {
        "metallicFactor": 0,
        "roughnessFactor": 0.9
      },
      "extensions": {
        "KHR_materials_unlit": {}
      },
      "doubleSided": true
    }
  ],
  "meshes": [
    {
      "primitives": [
        {
          "mode": 4,
          "attributes": {
            "POSITION": 0,
            "COLOR_0": 1
          },
          "material": 0
        }
      ]
    }
  ],
  "extensionsUsed": [
    "KHR_materials_unlit"
  ]
}

The accessors has an invalid range and the bufferviews are empty.

This PR prevents it to happens by skipping attributes that has no values, and primitives that has no attributes, and meshes that has no primitives.

I added a empty node and a button to test it. The expected output for the previous example will be:

{
  "asset": {
    "version": "2.0",
    "generator": "THREE.GLTFExporter"
  },
  "scenes": [
    {
      "nodes": [
        0
      ],
      "name": "AuxScene"
    }
  ],
  "scene": 0,
  "nodes": [
    {
      "name": "Custom buffered empty (drawrange)"
    }
  ]
}

@@ -484,6 +478,13 @@ THREE.GLTFExporter.prototype = {

}

// Skip creating an accessor if the attribute doesn't have data to export
if ( count === 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: missing space after 0

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@@ -1119,8 +1130,20 @@ THREE.GLTFExporter.prototype = {

}

if ( primitives.length === 0 ) {

return -1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In processNode we're returning null when no node is created. Would probably vote for null over -1 personally but either way let's make it consistent.

empty.name = 'Custom buffered empty (drawrange)';
scene1.add( empty );


Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about putting this case in GLTFExporter.tests.js instead? I think that might be a better fit than in the example.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah! I was thinking about it actually, but I didn't know we already had a GLTFExporter.tests.js 👼 I'll take a look and include it there thanks!


}

if ( geometry.index !== null ) {
// Skip meshes without exportable attributes
if ( Object.keys(primitive.attributes).length > 0 ) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need spaces after and before primitive.attributes

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! done

return null;

}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding

if ( Object.keys( attributes ).length === 0 ) {

    return null;

}

after

for ( var attributeName in geometry.attributes ) {

    ....

}

above instead isn't good?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any use case where you could have just morph attributes but no other attribute?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, no. At least, glTF validator seems to raise MESH_PRIMITIVE_MORPH_TARGET_NO_BASE_ACCESSOR "No base accessor for this attribute semantic." error if corresponding attribute isn't in attributes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah makes sense, added! thanks

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. So I think you can remove this check here now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mrdoob I believe he's referring to this #13681 (review) right? /cc @takahirox

Copy link
Collaborator

@takahirox takahirox Mar 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. I think we can remove here

if ( primitives.length === 0 ) {

	return null;

}

because we added above

if ( Object.keys( attributes ).length === 0 ) {

	return null;

}

Or you care the case where groups is an empty array? (In other words, how can primitives.length be zero here?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could have buffer geometries without groups eg: the sphere in the example https://github.com/mrdoob/three.js/blob/dev/examples/misc_exporter_gltf.html#L248 or a custom buffergeometry: https://github.com/mrdoob/three.js/blob/dev/examples/misc_exporter_gltf.html#L312-L337 but I haven't worked with multimaterial before so I'm not sure how that case would be handled here.
Because if you have one of the previous objects with a multimaterial.

var groups = isMultiMaterial ? mesh.geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ];

groups would be mesh.geometry.groups that would be empty.

Copy link
Collaborator

@takahirox takahirox Mar 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I confirmed rendering nothing for multi-material with empty groups.

So how about

if ( isMultiMaterial && mesh.geometry.groups.length === 0 ) return null;

it'd be more straightforward.

Copy link
Collaborator Author

@fernandojsg fernandojsg Mar 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fernandojsg
Copy link
Collaborator Author

@donmccurdy added unit test, although I can't get it running, are the examples test broken? I believe we should include three build on them or am I missing something?


}

if ( geometry.index !== null ) {
// Skip meshes without exportable attributes
if ( Object.keys( primitive.attributes ).length > 0 ) {
Copy link
Collaborator

@takahirox takahirox Mar 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove this check here now

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! removed!

@donmccurdy
Copy link
Collaborator

@fernandojsg They should be able to run in the browser if you use npm run dev-test and then open http://localhost:8080/test/unit/UnitTests.html, I don't know about running them via CLI.

@fernandojsg
Copy link
Collaborator Author

@donmccurdy thanks! I've just fixed it and test passed ^_^

@mrdoob mrdoob added this to the r92 milestone Mar 27, 2018
@mrdoob mrdoob merged commit 66ee44e into mrdoob:dev Mar 27, 2018
@mrdoob
Copy link
Owner

mrdoob commented Mar 27, 2018

Thanks!

@fernandojsg fernandojsg deleted the fixempty branch March 27, 2018 21:23
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

4 participants