diff --git a/CHANGELOG.md b/CHANGELOG.md index da21f3c..5adc6c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ * Added support for `VRMC_materials_mtoon` extension. +* Added support for `VRMC_node_constraint` extension. + ## 2.0.0-dev.3.10 ### New Features diff --git a/lib/src/base/gltf.dart b/lib/src/base/gltf.dart index b48f2cd..d0bc9aa 100644 --- a/lib/src/base/gltf.dart +++ b/lib/src/base/gltf.dart @@ -283,6 +283,7 @@ class Gltf extends GltfProperty { gltf.nodes.forEachWithIndices((i, node) { if (!node.isJoint && !node.isCollider && + !node.isConstraintSource && node.children == null && node.mesh == null && node.camera == null && diff --git a/lib/src/base/node.dart b/lib/src/base/node.dart index f51da94..5f22cbd 100644 --- a/lib/src/base/node.dart +++ b/lib/src/base/node.dart @@ -40,6 +40,7 @@ class Node extends GltfChildOfRootProperty { bool isJoint = false; bool isCollider = false; + bool isConstraintSource = false; Node._( this._cameraIndex, diff --git a/lib/src/ext/VRMC_node_constraint/vrmc_constraint.dart b/lib/src/ext/VRMC_node_constraint/vrmc_constraint.dart new file mode 100644 index 0000000..a7eec52 --- /dev/null +++ b/lib/src/ext/VRMC_node_constraint/vrmc_constraint.dart @@ -0,0 +1,229 @@ +/* + * # Copyright (c) 2024 Noeri Huisman + * # + * # Licensed under the Apache License, Version 2.0 (the "License"); + * # you may not use this file except in compliance with the License. + * # You may obtain a copy of the License at + * # + * # http://www.apache.org/licenses/LICENSE-2.0 + * # + * # Unless required by applicable law or agreed to in writing, software + * # distributed under the License is distributed on an "AS IS" BASIS, + * # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * # See the License for the specific language governing permissions and + * # limitations under the License. + */ + +library gltf.extensions.vrmc_vrm; + +import 'package:gltf/src/base/gltf_property.dart'; +import 'package:gltf/src/ext/extensions.dart'; + +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_node_constraint-1.0/schema/VRMC_node_constraint.constraint.schema.json +const String ROLL = 'roll'; +const String AIM = 'aim'; +const String ROTATION = 'rotation'; + +const List CONSTRAINT_MEMBERS = [ + ROLL, + AIM, + ROTATION, +]; + +class Constraint extends GltfProperty { + final RollConstraint roll; + final AimConstraint aim; + final RotationConstraint rotation; + + Constraint._(this.roll, this.aim, this.rotation, + Map extensions, Object extras) + : super(extensions, extras); + + static Constraint fromMap(Map map, Context context) { + if (context.validate) { + checkMembers(map, CONSTRAINT_MEMBERS, context); + } + + var total = (map.containsKey(ROLL) ? 1 : 0) + + (map.containsKey(AIM) ? 1 : 0) + + (map.containsKey(ROTATION) ? 1 : 0); + if (context.validate && total != 1) { + context.addIssue(SchemaError.oneOfMismatch, args: CONSTRAINT_MEMBERS); + } + + return Constraint._( + getObjectFromInnerMap(map, ROLL, context, RollConstraint.fromMap), + getObjectFromInnerMap(map, AIM, context, AimConstraint.fromMap), + getObjectFromInnerMap( + map, ROTATION, context, RotationConstraint.fromMap), + getExtensions(map, Constraint, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + if (roll != null) { + context.path.add(ROLL); + roll.link(gltf, context); + context.path.removeLast(); + } + + if (aim != null) { + context.path.add(AIM); + aim.link(gltf, context); + context.path.removeLast(); + } + + if (rotation != null) { + context.path.add(ROTATION); + rotation.link(gltf, context); + context.path.removeLast(); + } + } +} + +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_node_constraint-1.0/schema/VRMC_node_constraint.rollConstraint.schema.json +const String SOURCE = 'source'; +const String ROLL_AXIS = 'rollAxis'; +const String WEIGHT = 'weight'; +const List ROLL_CONSTRAINT_MEMBERS = [ + SOURCE, + ROLL_AXIS, + WEIGHT, +]; + +class RollConstraint extends GltfProperty { + final int source; + final String rollAxis; + final double weight; + + RollConstraint._(this.source, this.rollAxis, this.weight, + Map extensions, Object extras) + : super(extensions, extras); + + static RollConstraint fromMap(Map map, Context context) { + if (context.validate) { + checkMembers(map, ROLL_CONSTRAINT_MEMBERS, context); + } + + return RollConstraint._( + getIndex(map, SOURCE, context), + getString(map, ROLL_AXIS, context, req: true, list: ['X', 'Y', 'Z']), + getFloat(map, WEIGHT, context, min: 0.0, max: 1.0, def: 1.0), + getExtensions(map, RollConstraint, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + var node = gltf.nodes[source]; + + if (context.validate && source != -1) { + if (node == null) { + context.addIssue(LinkError.unresolvedReference, + name: SOURCE, args: [source]); + } else { + node + ..markAsUsed() + ..isConstraintSource = true; + } + } + } +} + +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_node_constraint-1.0/schema/VRMC_node_constraint.aimConstraint.schema.json +const String AIM_AXIS = 'aimAxis'; +const List AIM_CONSTRAINT_MEMBERS = [ + SOURCE, + AIM_AXIS, + WEIGHT, +]; + +class AimConstraint extends GltfProperty { + final int source; + final String aimAxis; + final double weight; + + AimConstraint._(this.source, this.aimAxis, this.weight, + Map extensions, Object extras) + : super(extensions, extras); + + static AimConstraint fromMap(Map map, Context context) { + if (context.validate) { + checkMembers(map, AIM_CONSTRAINT_MEMBERS, context); + } + + return AimConstraint._( + getIndex(map, SOURCE, context), + getString(map, AIM_AXIS, context, req: true, list: [ + 'PositiveX', + 'NegativeX', + 'PositiveY', + 'NegativeY', + 'PositiveZ', + 'NegativeZ' + ]), + getFloat(map, WEIGHT, context, min: 0.0, max: 1.0, def: 1.0), + getExtensions(map, AimConstraint, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + var node = gltf.nodes[source]; + + if (context.validate && source != -1) { + if (node == null) { + context.addIssue(LinkError.unresolvedReference, + name: SOURCE, args: [source]); + } else { + node + ..markAsUsed() + ..isConstraintSource = true; + } + } + } +} + +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_node_constraint-1.0/schema/VRMC_node_constraint.rotationConstraint.schema.json +const List ROTATION_CONSTRAINT_MEMBERS = [ + SOURCE, + WEIGHT, +]; + +class RotationConstraint extends GltfProperty { + final int source; + final double weight; + + RotationConstraint._( + this.source, this.weight, Map extensions, Object extras) + : super(extensions, extras); + + static RotationConstraint fromMap(Map map, Context context) { + if (context.validate) { + checkMembers(map, ROTATION_CONSTRAINT_MEMBERS, context); + } + + return RotationConstraint._( + getIndex(map, SOURCE, context), + getFloat(map, WEIGHT, context, min: 0.0, max: 1.0, def: 1.0), + getExtensions(map, RotationConstraint, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + var node = gltf.nodes[source]; + + if (context.validate && source != -1) { + if (node == null) { + context.addIssue(LinkError.unresolvedReference, + name: SOURCE, args: [source]); + } else { + node + ..markAsUsed() + ..isConstraintSource = true; + } + } + } +} diff --git a/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint.dart b/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint.dart new file mode 100644 index 0000000..ade0e98 --- /dev/null +++ b/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint.dart @@ -0,0 +1,70 @@ +/* + * # Copyright (c) 2024 Noeri Huisman + * # + * # Licensed under the Apache License, Version 2.0 (the "License"); + * # you may not use this file except in compliance with the License. + * # You may obtain a copy of the License at + * # + * # http://www.apache.org/licenses/LICENSE-2.0 + * # + * # Unless required by applicable law or agreed to in writing, software + * # distributed under the License is distributed on an "AS IS" BASIS, + * # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * # See the License for the specific language governing permissions and + * # limitations under the License. + */ + +library gltf.extensions.vrmc_vrm; + +import 'package:gltf/src/base/gltf_property.dart'; +import 'package:gltf/src/ext/VRMC_node_constraint/vrmc_constraint.dart'; +import 'package:gltf/src/ext/extensions.dart'; + +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_node_constraint-1.0/schema/VRMC_node_constraint.schema.json +const String VRMC_NODE_CONSTRAINT = 'VRMC_node_constraint'; +const String SPEC_VERSION = 'specVersion'; +const String CONSTRAINT = 'constraint'; + +const List VRMC_NODE_CONSTRAINT_MEMBERS = [ + SPEC_VERSION, + CONSTRAINT, +]; + +class VrmcNodeConstraint extends GltfProperty { + final String specVersion; + final Constraint constraint; + + VrmcNodeConstraint._(this.specVersion, this.constraint, + Map extensions, Object extras) + : super(extensions, extras); + + static VrmcNodeConstraint fromMap(Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_NODE_CONSTRAINT_MEMBERS, context); + } + + final specVersion = getString(map, SPEC_VERSION, context, + req: true, regexp: RegExp(r'^1\.0$')); + + return VrmcNodeConstraint._( + specVersion, + getObjectFromInnerMap(map, CONSTRAINT, context, Constraint.fromMap, + req: true), + getExtensions(map, VrmcNodeConstraint, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + if (constraint != null) { + context.path.add(CONSTRAINT); + constraint.link(gltf, context); + context.path.removeLast(); + } + } +} + +const Extension vrmcNodeConstraint = + Extension(VRMC_NODE_CONSTRAINT, { + Node: ExtensionDescriptor(VrmcNodeConstraint.fromMap), +}); diff --git a/lib/src/ext/extensions.dart b/lib/src/ext/extensions.dart index aa9b184..3dad582 100644 --- a/lib/src/ext/extensions.dart +++ b/lib/src/ext/extensions.dart @@ -36,6 +36,7 @@ import 'package:gltf/src/ext/KHR_materials_volume/khr_materials_volume.dart'; import 'package:gltf/src/ext/KHR_mesh_quantization/khr_mesh_quantization.dart'; import 'package:gltf/src/ext/KHR_texture_transform/khr_texture_transform.dart'; import 'package:gltf/src/ext/VRMC_materials_mtoon/vrmc_materials_mtoon.dart'; +import 'package:gltf/src/ext/VRMC_node_constraint/vrmc_node_constraint.dart'; import 'package:gltf/src/ext/VRMC_springBone/vrmc_spring_bone.dart'; import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm.dart'; import 'package:gltf/src/hash.dart'; @@ -130,6 +131,7 @@ const List kDefaultExtensions = [ khrMeshQuantizationExtension, khrTextureTransformExtension, vrmcMaterialMtoon, + vrmcNodeConstraint, vrmcSpringBone, vrmcVrm, ];