Skip to content

Commit

Permalink
Support morph targets with indexed attributes (#221)
Browse files Browse the repository at this point in the history
  • Loading branch information
lexaknyazev authored Jun 2, 2024
1 parent 6882e43 commit 146d44d
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 40 deletions.
5 changes: 5 additions & 0 deletions lib/src/base/members.dart
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,11 @@ const List<String> ATTRIBUTE_SEMANTIC_ARRAY_MEMBERS = <String>[
WEIGHTS_
];

const List<String> ATTRIBUTE_SEMANTIC_MORPH_TARGET_ARRAY_MEMBERS = <String>[
COLOR_,
TEXCOORD_,
];

const Map<int, String> ATTRIBUTE_TYPES = <int, String>{
gl.FLOAT: SCALAR,
gl.FLOAT_VEC2: VEC2,
Expand Down
59 changes: 33 additions & 26 deletions lib/src/base/mesh.dart
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,22 @@ class MeshPrimitive extends GltfProperty {
var texCoordCount = 0;
var maxTexcoord = -1;

int parseAttributeSemanticIndex(List<int> codeUnits) {
if (codeUnits.isEmpty || codeUnits.length > 1 && codeUnits[0] == 0x30) {
return -1;
}

var index = 0;
for (var i = 0; i < codeUnits.length; ++i) {
final digit = codeUnits[i] - 0x30;
if (digit > 9 || digit < 0) {
return -1;
}
index = 10 * index + digit;
}
return index;
}

void checkAttributeSemanticName(String semantic) {
// Skip on custom semantics
if (semantic.isNotEmpty && semantic.codeUnitAt(0) == 95 /*underscore*/) {
Expand Down Expand Up @@ -194,29 +210,8 @@ class MeshPrimitive extends GltfProperty {
break;
}

var index = 0;
var valid = true;
final codeUnits = semParts[1].codeUnits;

if (codeUnits.isEmpty) {
valid = false;
} else if (codeUnits.length == 1) {
index = codeUnits[0] - 0x30 /* 0 */;
if (index < 0 || index > 9) {
valid = false;
}
} else {
for (var i = 0; i < codeUnits.length; ++i) {
final digit = codeUnits[i] - 0x30;
if (digit > 9 || digit < 0 || i == 0 && digit == 0) {
valid = false;
break;
}
index = 10 * index + digit;
}
}

if (valid) {
final index = parseAttributeSemanticIndex(semParts[1].codeUnits);
if (index != -1) {
switch (arraySemantic) {
case COLOR_:
colorCount++;
Expand Down Expand Up @@ -289,8 +284,20 @@ class MeshPrimitive extends GltfProperty {
}

void checkMorphTargetAttributeSemanticName(String semantic) {
if (!context.morphAttributeAccessorFormats.containsKey(semantic) &&
!semantic.startsWith('_')) {
// Skip custom semantics
if (semantic.isNotEmpty && semantic.codeUnitAt(0) == 95 /*underscore*/) {
return;
}

if (ATTRIBUTE_SEMANTIC_MEMBERS.contains(semantic)) {
return;
}

final semParts = semantic.split('_');
if (!ATTRIBUTE_SEMANTIC_MORPH_TARGET_ARRAY_MEMBERS
.contains(semParts[0]) ||
semParts.length != 2 ||
parseAttributeSemanticIndex(semParts[1].codeUnits) == -1) {
context.addIssue(SemanticError.meshPrimitiveInvalidAttribute,
name: semantic);
}
Expand Down Expand Up @@ -597,7 +604,7 @@ class MeshPrimitive extends GltfProperty {

final format = AccessorFormat.fromAccessor(accessor);
final validFormats =
context.morphAttributeAccessorFormats[semantic];
context.morphAttributeAccessorFormats[semantic.split('_')[0]];

if (validFormats != null && !validFormats.contains(format)) {
context.addIssue(
Expand Down
3 changes: 3 additions & 0 deletions test/base/base_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -420,11 +420,14 @@ Future main() async {
final mesh = gltf.meshes[0];
final primitive = mesh.primitives[0];
expect(primitive.attributes['POSITION'], gltf.accessors[1]);
expect(primitive.attributes['COLOR_0'], gltf.accessors[4]);
expect(primitive.indices, gltf.accessors[0]);
expect(primitive.material, gltf.materials[0]);
expect(primitive.mode, 4);
expect(primitive.targets[0]['POSITION'], gltf.accessors[2]);
expect(primitive.targets[1]['POSITION'], gltf.accessors[3]);
expect(primitive.targets[0]['COLOR_0'], gltf.accessors[5]);
expect(primitive.targets[1]['COLOR_0'], gltf.accessors[6]);
expect(primitive.extensions, isEmpty);
expect(mesh.weights, [0.7, 0.2]);
expect(mesh.extensions, isEmpty);
Expand Down
20 changes: 20 additions & 0 deletions test/base/data/mesh/invalid_indexed_attribute.gltf
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,26 @@
}
}
]
},
{
"primitives": [
{
"attributes": {
"POSITION": 0
},
"targets": [
{
"TEXCOORD_00": 1,
"TEXCOORD_01": 1,
"TEXCOORD_1": 1,
"INVALID_": 0,
"INVALID_1_1": 0,
"INVALID_11": 0,
"INVALID_D": 0
}
]
}
]
}
],
"accessors": [
Expand Down
98 changes: 91 additions & 7 deletions test/base/data/mesh/invalid_indexed_attribute.gltf.report.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"uri": "test/base/data/mesh/invalid_indexed_attribute.gltf",
"mimeType": "model/gltf+json",
"validatorVersion": "2.0.0-dev.3.0",
"validatorVersion": "2.0.0-dev.3.10",
"issues": {
"numErrors": 8,
"numErrors": 21,
"numWarnings": 0,
"numInfos": 1,
"numInfos": 2,
"numHints": 0,
"messages": [
{
Expand Down Expand Up @@ -56,11 +56,95 @@
"severity": 0,
"pointer": "/meshes/0/primitives/0/attributes"
},
{
"code": "MESH_PRIMITIVE_INVALID_ATTRIBUTE",
"message": "Invalid attribute name.",
"severity": 0,
"pointer": "/meshes/1/primitives/0/targets/0/TEXCOORD_00"
},
{
"code": "MESH_PRIMITIVE_INVALID_ATTRIBUTE",
"message": "Invalid attribute name.",
"severity": 0,
"pointer": "/meshes/1/primitives/0/targets/0/TEXCOORD_01"
},
{
"code": "MESH_PRIMITIVE_INVALID_ATTRIBUTE",
"message": "Invalid attribute name.",
"severity": 0,
"pointer": "/meshes/1/primitives/0/targets/0/INVALID_"
},
{
"code": "MESH_PRIMITIVE_INVALID_ATTRIBUTE",
"message": "Invalid attribute name.",
"severity": 0,
"pointer": "/meshes/1/primitives/0/targets/0/INVALID_1_1"
},
{
"code": "MESH_PRIMITIVE_INVALID_ATTRIBUTE",
"message": "Invalid attribute name.",
"severity": 0,
"pointer": "/meshes/1/primitives/0/targets/0/INVALID_11"
},
{
"code": "MESH_PRIMITIVE_INVALID_ATTRIBUTE",
"message": "Invalid attribute name.",
"severity": 0,
"pointer": "/meshes/1/primitives/0/targets/0/INVALID_D"
},
{
"code": "MESH_PRIMITIVE_MORPH_TARGET_NO_BASE_ACCESSOR",
"message": "The mesh primitive does not define this attribute semantic.",
"severity": 0,
"pointer": "/meshes/1/primitives/0/targets/0/TEXCOORD_00"
},
{
"code": "MESH_PRIMITIVE_MORPH_TARGET_NO_BASE_ACCESSOR",
"message": "The mesh primitive does not define this attribute semantic.",
"severity": 0,
"pointer": "/meshes/1/primitives/0/targets/0/TEXCOORD_01"
},
{
"code": "MESH_PRIMITIVE_MORPH_TARGET_NO_BASE_ACCESSOR",
"message": "The mesh primitive does not define this attribute semantic.",
"severity": 0,
"pointer": "/meshes/1/primitives/0/targets/0/TEXCOORD_1"
},
{
"code": "MESH_PRIMITIVE_MORPH_TARGET_NO_BASE_ACCESSOR",
"message": "The mesh primitive does not define this attribute semantic.",
"severity": 0,
"pointer": "/meshes/1/primitives/0/targets/0/INVALID_"
},
{
"code": "MESH_PRIMITIVE_MORPH_TARGET_NO_BASE_ACCESSOR",
"message": "The mesh primitive does not define this attribute semantic.",
"severity": 0,
"pointer": "/meshes/1/primitives/0/targets/0/INVALID_1_1"
},
{
"code": "MESH_PRIMITIVE_MORPH_TARGET_NO_BASE_ACCESSOR",
"message": "The mesh primitive does not define this attribute semantic.",
"severity": 0,
"pointer": "/meshes/1/primitives/0/targets/0/INVALID_11"
},
{
"code": "MESH_PRIMITIVE_MORPH_TARGET_NO_BASE_ACCESSOR",
"message": "The mesh primitive does not define this attribute semantic.",
"severity": 0,
"pointer": "/meshes/1/primitives/0/targets/0/INVALID_D"
},
{
"code": "UNUSED_OBJECT",
"message": "This object may be unused.",
"severity": 2,
"pointer": "/meshes/0"
},
{
"code": "UNUSED_OBJECT",
"message": "This object may be unused.",
"severity": 2,
"pointer": "/meshes/1"
}
],
"truncated": false
Expand All @@ -69,13 +153,13 @@
"version": "2.0",
"animationCount": 0,
"materialCount": 0,
"hasMorphTargets": false,
"hasMorphTargets": true,
"hasSkins": false,
"hasTextures": false,
"hasDefaultScene": false,
"drawCallCount": 1,
"totalVertexCount": 3,
"totalTriangleCount": 1,
"drawCallCount": 2,
"totalVertexCount": 6,
"totalTriangleCount": 2,
"maxUVs": 0,
"maxInfluences": 0,
"maxAttributes": 9
Expand Down
28 changes: 23 additions & 5 deletions test/base/data/mesh/valid.gltf
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@
"primitives": [
{
"attributes": {
"POSITION": 1
"POSITION": 1,
"COLOR_0": 4
},
"mode": 4,
"indices": 0,
"material": 0,
"targets": [
{
"POSITION": 2
"POSITION": 2,
"COLOR_0": 5
},
{
"POSITION": 3
"POSITION": 3,
"COLOR_0": 6
}
]
}
Expand Down Expand Up @@ -60,8 +63,8 @@
1.0,
1.0
]
}
,{
},
{
"type": "VEC3",
"componentType": 5126,
"count": 4,
Expand All @@ -75,6 +78,21 @@
1.0,
1.0
]
},
{
"type": "VEC3",
"componentType": 5126,
"count": 4
},
{
"type": "VEC3",
"componentType": 5126,
"count": 4
},
{
"type": "VEC3",
"componentType": 5126,
"count": 4
}
],
"materials": [
Expand Down
4 changes: 2 additions & 2 deletions test/base/data/mesh/valid.gltf.report.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"uri": "test/base/data/mesh/valid.gltf",
"mimeType": "model/gltf+json",
"validatorVersion": "2.0.0-dev.3.0",
"validatorVersion": "2.0.0-dev.3.10",
"issues": {
"numErrors": 0,
"numWarnings": 0,
Expand Down Expand Up @@ -30,6 +30,6 @@
"totalTriangleCount": 2,
"maxUVs": 0,
"maxInfluences": 0,
"maxAttributes": 1
"maxAttributes": 2
}
}

0 comments on commit 146d44d

Please sign in to comment.