From 92dd75d25f6eae01e18ee07449e47745cfdd1350 Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Fri, 29 Jul 2022 23:48:24 +0900 Subject: [PATCH 01/18] wip feature: support VRMC_vrm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ✅ specVersion - ✅ meta - ✅ humanoid Test should be written later --- lib/src/errors.dart | 4 + lib/src/ext/VRMC_vrm/vrmc_vrm.dart | 78 ++++ lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart | 473 ++++++++++++++++++++ lib/src/ext/VRMC_vrm/vrmc_vrm_meta.dart | 226 ++++++++++ lib/src/ext/extensions.dart | 4 +- 5 files changed, 784 insertions(+), 1 deletion(-) create mode 100644 lib/src/ext/VRMC_vrm/vrmc_vrm.dart create mode 100644 lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart create mode 100644 lib/src/ext/VRMC_vrm/vrmc_vrm_meta.dart diff --git a/lib/src/errors.dart b/lib/src/errors.dart index 4e57567..1a2ed0c 100644 --- a/lib/src/errors.dart +++ b/lib/src/errors.dart @@ -780,6 +780,10 @@ class LinkError extends IssueType { 'KHR_MATERIALS_VARIANTS_NON_UNIQUE_VARIANT', (args) => 'This variant is used more than once for this mesh primitive.'); + static final LinkError vrmcVrmInvalidHumanoidHierarchy = LinkError._( + 'VRMC_VRM_INVALID_HUMANOID_HIERARCHY', + (args) => '${_q(args[0])} must be a descendant of ${_q(args[1])}'); + LinkError._(String type, ErrorFunction message, [Severity severity = Severity.Error]) : super(type, message, severity); diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm.dart new file mode 100644 index 0000000..71810a1 --- /dev/null +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm.dart @@ -0,0 +1,78 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_vrm/vrmc_vrm_humanoid.dart'; +import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm_meta.dart'; +import 'package:gltf/src/ext/extensions.dart'; + +const String VRMC_VRM = 'VRMC_vrm'; +const String SPEC_VERSION = 'specVersion'; +const String META = 'meta'; +const String HUMANOID = 'humanoid'; + +const List VRMC_VRM_MEMBERS = [ + SPEC_VERSION, + META, + HUMANOID, +]; + +const String SPEC_VERSION_10_BETA = '1.0-beta'; + +const List VRMC_VRM_SPEC_VERSIONS = [ + SPEC_VERSION_10_BETA, +]; + +class VrmcVrm extends GltfProperty { + final String specVersion; + final VrmcVrmMeta meta; + final VrmcVrmHumanoid humanoid; + + VrmcVrm._(this.specVersion, this.meta, this.humanoid, + Map extensions, Object extras) + : super(extensions, extras); + + static VrmcVrm fromMap(Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_VRM_MEMBERS, context); + } + + final specVersion = getString(map, SPEC_VERSION, context, + list: VRMC_VRM_SPEC_VERSIONS, req: true); + + return VrmcVrm._( + specVersion, + getObjectFromInnerMap(map, META, context, VrmcVrmMeta.fromMap, + req: true), + getObjectFromInnerMap(map, HUMANOID, context, VrmcVrmHumanoid.fromMap, + req: true), + getExtensions(map, VrmcVrm, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + meta?.link(gltf, context); + humanoid?.link(gltf, context); + } +} + +const Extension vrmcVrmExtension = + Extension(VRMC_VRM, { + Gltf: ExtensionDescriptor(VrmcVrm.fromMap), +}); diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart new file mode 100644 index 0000000..cc78f72 --- /dev/null +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart @@ -0,0 +1,473 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_humanoid; + +import 'package:gltf/src/base/gltf_property.dart'; + +const List VRMC_VRM_HUMANOID_HUMAN_BONE_MEMBERS = [ + NODE, +]; + +class VrmcVrmHumanoidHumanBone extends GltfProperty { + final int nodeIndex; + + Node node; + + VrmcVrmHumanoidHumanBone._( + this.nodeIndex, Map extensions, Object extras) + : super(extensions, extras); + + static VrmcVrmHumanoidHumanBone fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_VRM_HUMANOID_HUMAN_BONE_MEMBERS, context); + } + + final index = getIndex(map, NODE, context, req: true); + + return VrmcVrmHumanoidHumanBone._(index, + getExtensions(map, VrmcVrmHumanoid, context), getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + node = gltf.nodes[nodeIndex]; + if (context.validate && nodeIndex != -1) { + if (node == null) { + context.addIssue(LinkError.unresolvedReference, + name: NODE, args: [nodeIndex]); + } else { + node.markAsUsed(); + } + } + } +} + +const String HIPS = 'hips'; +const String SPINE = 'spine'; +const String CHEST = 'chest'; +const String UPPER_CHEST = 'upperChest'; +const String NECK = 'neck'; +const String HEAD = 'head'; +const String LEFT_EYE = 'leftEye'; +const String RIGHT_EYE = 'rightEye'; +const String JAW = 'jaw'; +const String LEFT_UPPER_LEG = 'leftUpperLeg'; +const String LEFT_LOWER_LEG = 'leftLowerLeg'; +const String LEFT_FOOT = 'leftFoot'; +const String LEFT_TOES = 'leftToes'; +const String RIGHT_UPPER_LEG = 'rightUpperLeg'; +const String RIGHT_LOWER_LEG = 'rightLowerLeg'; +const String RIGHT_FOOT = 'rightFoot'; +const String RIGHT_TOES = 'rightToes'; +const String LEFT_SHOULDER = 'leftShoulder'; +const String LEFT_UPPER_ARM = 'leftUpperArm'; +const String LEFT_LOWER_ARM = 'leftLowerArm'; +const String LEFT_HAND = 'leftHand'; +const String RIGHT_SHOULDER = 'rightShoulder'; +const String RIGHT_UPPER_ARM = 'rightUpperArm'; +const String RIGHT_LOWER_ARM = 'rightLowerArm'; +const String RIGHT_HAND = 'rightHand'; +const String LEFT_THUMB_METACARPAL = 'leftThumbMetacarpal'; +const String LEFT_THUMB_PROXIMAL = 'leftThumbProximal'; +const String LEFT_THUMB_DISTAL = 'leftThumbDistal'; +const String LEFT_INDEX_PROXIMAL = 'leftIndexProximal'; +const String LEFT_INDEX_INTERMEDIATE = 'leftIndexIntermediate'; +const String LEFT_INDEX_DISTAL = 'leftIndexDistal'; +const String LEFT_MIDDLE_PROXIMAL = 'leftMiddleProximal'; +const String LEFT_MIDDLE_INTERMEDIATE = 'leftMiddleIntermediate'; +const String LEFT_MIDDLE_DISTAL = 'leftMiddleDistal'; +const String LEFT_RING_PROXIMAL = 'leftRingProximal'; +const String LEFT_RING_INTERMEDIATE = 'leftRingIntermediate'; +const String LEFT_RING_DISTAL = 'leftRingDistal'; +const String LEFT_LITTLE_PROXIMAL = 'leftLittleProximal'; +const String LEFT_LITTLE_INTERMEDIATE = 'leftLittleIntermediate'; +const String LEFT_LITTLE_DISTAL = 'leftLittleDistal'; +const String RIGHT_THUMB_METACARPAL = 'rightThumbMetacarpal'; +const String RIGHT_THUMB_PROXIMAL = 'rightThumbProximal'; +const String RIGHT_THUMB_DISTAL = 'rightThumbDistal'; +const String RIGHT_INDEX_PROXIMAL = 'rightIndexProximal'; +const String RIGHT_INDEX_INTERMEDIATE = 'rightIndexIntermediate'; +const String RIGHT_INDEX_DISTAL = 'rightIndexDistal'; +const String RIGHT_MIDDLE_PROXIMAL = 'rightMiddleProximal'; +const String RIGHT_MIDDLE_INTERMEDIATE = 'rightMiddleIntermediate'; +const String RIGHT_MIDDLE_DISTAL = 'rightMiddleDistal'; +const String RIGHT_RING_PROXIMAL = 'rightRingProximal'; +const String RIGHT_RING_INTERMEDIATE = 'rightRingIntermediate'; +const String RIGHT_RING_DISTAL = 'rightRingDistal'; +const String RIGHT_LITTLE_PROXIMAL = 'rightLittleProximal'; +const String RIGHT_LITTLE_INTERMEDIATE = 'rightLittleIntermediate'; +const String RIGHT_LITTLE_DISTAL = 'rightLittleDistal'; + +const List VRMC_VRM_HUMANOID_HUMAN_BONES_MEMBERS = [ + HIPS, + SPINE, + CHEST, + UPPER_CHEST, + NECK, + HEAD, + LEFT_EYE, + RIGHT_EYE, + JAW, + LEFT_UPPER_LEG, + LEFT_LOWER_LEG, + LEFT_FOOT, + LEFT_TOES, + RIGHT_UPPER_LEG, + RIGHT_LOWER_LEG, + RIGHT_FOOT, + RIGHT_TOES, + LEFT_SHOULDER, + LEFT_UPPER_ARM, + LEFT_LOWER_ARM, + LEFT_HAND, + RIGHT_SHOULDER, + RIGHT_UPPER_ARM, + RIGHT_LOWER_ARM, + RIGHT_HAND, + LEFT_THUMB_METACARPAL, + LEFT_THUMB_PROXIMAL, + LEFT_THUMB_DISTAL, + LEFT_INDEX_PROXIMAL, + LEFT_INDEX_INTERMEDIATE, + LEFT_INDEX_DISTAL, + LEFT_MIDDLE_PROXIMAL, + LEFT_MIDDLE_INTERMEDIATE, + LEFT_MIDDLE_DISTAL, + LEFT_RING_PROXIMAL, + LEFT_RING_INTERMEDIATE, + LEFT_RING_DISTAL, + LEFT_LITTLE_PROXIMAL, + LEFT_LITTLE_INTERMEDIATE, + LEFT_LITTLE_DISTAL, + RIGHT_THUMB_METACARPAL, + RIGHT_THUMB_PROXIMAL, + RIGHT_THUMB_DISTAL, + RIGHT_INDEX_PROXIMAL, + RIGHT_INDEX_INTERMEDIATE, + RIGHT_INDEX_DISTAL, + RIGHT_MIDDLE_PROXIMAL, + RIGHT_MIDDLE_INTERMEDIATE, + RIGHT_MIDDLE_DISTAL, + RIGHT_RING_PROXIMAL, + RIGHT_RING_INTERMEDIATE, + RIGHT_RING_DISTAL, + RIGHT_LITTLE_PROXIMAL, + RIGHT_LITTLE_INTERMEDIATE, + RIGHT_LITTLE_DISTAL, +]; + +const Map VRMC_VRM_HUMANOID_HUMAN_BONES_REQUIRED = { + HIPS: true, + SPINE: true, + CHEST: false, + UPPER_CHEST: false, + NECK: false, + HEAD: true, + LEFT_EYE: false, + RIGHT_EYE: false, + JAW: false, + LEFT_UPPER_LEG: true, + LEFT_LOWER_LEG: true, + LEFT_FOOT: true, + LEFT_TOES: false, + RIGHT_UPPER_LEG: true, + RIGHT_LOWER_LEG: true, + RIGHT_FOOT: true, + RIGHT_TOES: false, + LEFT_SHOULDER: false, + LEFT_UPPER_ARM: true, + LEFT_LOWER_ARM: true, + LEFT_HAND: true, + RIGHT_SHOULDER: false, + RIGHT_UPPER_ARM: true, + RIGHT_LOWER_ARM: true, + RIGHT_HAND: true, + LEFT_THUMB_METACARPAL: false, + LEFT_THUMB_PROXIMAL: false, + LEFT_THUMB_DISTAL: false, + LEFT_INDEX_PROXIMAL: false, + LEFT_INDEX_INTERMEDIATE: false, + LEFT_INDEX_DISTAL: false, + LEFT_MIDDLE_PROXIMAL: false, + LEFT_MIDDLE_INTERMEDIATE: false, + LEFT_MIDDLE_DISTAL: false, + LEFT_RING_PROXIMAL: false, + LEFT_RING_INTERMEDIATE: false, + LEFT_RING_DISTAL: false, + LEFT_LITTLE_PROXIMAL: false, + LEFT_LITTLE_INTERMEDIATE: false, + LEFT_LITTLE_DISTAL: false, + RIGHT_THUMB_METACARPAL: false, + RIGHT_THUMB_PROXIMAL: false, + RIGHT_THUMB_DISTAL: false, + RIGHT_INDEX_PROXIMAL: false, + RIGHT_INDEX_INTERMEDIATE: false, + RIGHT_INDEX_DISTAL: false, + RIGHT_MIDDLE_PROXIMAL: false, + RIGHT_MIDDLE_INTERMEDIATE: false, + RIGHT_MIDDLE_DISTAL: false, + RIGHT_RING_PROXIMAL: false, + RIGHT_RING_INTERMEDIATE: false, + RIGHT_RING_DISTAL: false, + RIGHT_LITTLE_PROXIMAL: false, + RIGHT_LITTLE_INTERMEDIATE: false, + RIGHT_LITTLE_DISTAL: false, +}; + +const Map VRMC_VRM_HUMANOID_HUMAN_BONES_NEEDS_PARENT = { + HIPS: false, + SPINE: false, + CHEST: false, + UPPER_CHEST: true, + NECK: false, + HEAD: false, + LEFT_EYE: false, + RIGHT_EYE: false, + JAW: false, + LEFT_UPPER_LEG: false, + LEFT_LOWER_LEG: false, + LEFT_FOOT: false, + LEFT_TOES: false, + RIGHT_UPPER_LEG: false, + RIGHT_LOWER_LEG: false, + RIGHT_FOOT: false, + RIGHT_TOES: false, + LEFT_SHOULDER: false, + LEFT_UPPER_ARM: false, + LEFT_LOWER_ARM: false, + LEFT_HAND: false, + RIGHT_SHOULDER: false, + RIGHT_UPPER_ARM: false, + RIGHT_LOWER_ARM: false, + RIGHT_HAND: false, + LEFT_THUMB_METACARPAL: false, + LEFT_THUMB_PROXIMAL: true, + LEFT_THUMB_DISTAL: true, + LEFT_INDEX_PROXIMAL: false, + LEFT_INDEX_INTERMEDIATE: true, + LEFT_INDEX_DISTAL: true, + LEFT_MIDDLE_PROXIMAL: false, + LEFT_MIDDLE_INTERMEDIATE: true, + LEFT_MIDDLE_DISTAL: true, + LEFT_RING_PROXIMAL: false, + LEFT_RING_INTERMEDIATE: true, + LEFT_RING_DISTAL: true, + LEFT_LITTLE_PROXIMAL: false, + LEFT_LITTLE_INTERMEDIATE: true, + LEFT_LITTLE_DISTAL: true, + RIGHT_THUMB_METACARPAL: false, + RIGHT_THUMB_PROXIMAL: true, + RIGHT_THUMB_DISTAL: true, + RIGHT_INDEX_PROXIMAL: false, + RIGHT_INDEX_INTERMEDIATE: true, + RIGHT_INDEX_DISTAL: true, + RIGHT_MIDDLE_PROXIMAL: false, + RIGHT_MIDDLE_INTERMEDIATE: true, + RIGHT_MIDDLE_DISTAL: true, + RIGHT_RING_PROXIMAL: false, + RIGHT_RING_INTERMEDIATE: true, + RIGHT_RING_DISTAL: true, + RIGHT_LITTLE_PROXIMAL: false, + RIGHT_LITTLE_INTERMEDIATE: true, + RIGHT_LITTLE_DISTAL: true, +}; + +const Map VRMC_VRM_HUMANOID_HUMAN_BONES_PARENT_MAP = { + HIPS: null, + SPINE: HIPS, + CHEST: SPINE, + UPPER_CHEST: CHEST, + NECK: UPPER_CHEST, + HEAD: NECK, + LEFT_EYE: HEAD, + RIGHT_EYE: HEAD, + JAW: HEAD, + LEFT_UPPER_LEG: HIPS, + LEFT_LOWER_LEG: LEFT_UPPER_LEG, + LEFT_FOOT: LEFT_LOWER_LEG, + LEFT_TOES: LEFT_FOOT, + RIGHT_UPPER_LEG: HIPS, + RIGHT_LOWER_LEG: RIGHT_UPPER_LEG, + RIGHT_FOOT: RIGHT_LOWER_LEG, + RIGHT_TOES: RIGHT_FOOT, + LEFT_SHOULDER: UPPER_CHEST, + LEFT_UPPER_ARM: LEFT_SHOULDER, + LEFT_LOWER_ARM: LEFT_UPPER_ARM, + LEFT_HAND: LEFT_LOWER_ARM, + RIGHT_SHOULDER: UPPER_CHEST, + RIGHT_UPPER_ARM: RIGHT_SHOULDER, + RIGHT_LOWER_ARM: RIGHT_UPPER_ARM, + RIGHT_HAND: RIGHT_LOWER_ARM, + LEFT_THUMB_METACARPAL: LEFT_HAND, + LEFT_THUMB_PROXIMAL: LEFT_THUMB_METACARPAL, + LEFT_THUMB_DISTAL: LEFT_THUMB_PROXIMAL, + LEFT_INDEX_PROXIMAL: LEFT_HAND, + LEFT_INDEX_INTERMEDIATE: LEFT_INDEX_PROXIMAL, + LEFT_INDEX_DISTAL: LEFT_INDEX_INTERMEDIATE, + LEFT_MIDDLE_PROXIMAL: LEFT_HAND, + LEFT_MIDDLE_INTERMEDIATE: LEFT_MIDDLE_PROXIMAL, + LEFT_MIDDLE_DISTAL: LEFT_MIDDLE_INTERMEDIATE, + LEFT_RING_PROXIMAL: LEFT_HAND, + LEFT_RING_INTERMEDIATE: LEFT_RING_PROXIMAL, + LEFT_RING_DISTAL: LEFT_RING_INTERMEDIATE, + LEFT_LITTLE_PROXIMAL: LEFT_HAND, + LEFT_LITTLE_INTERMEDIATE: LEFT_LITTLE_PROXIMAL, + LEFT_LITTLE_DISTAL: LEFT_LITTLE_INTERMEDIATE, + RIGHT_THUMB_METACARPAL: RIGHT_HAND, + RIGHT_THUMB_PROXIMAL: RIGHT_THUMB_METACARPAL, + RIGHT_THUMB_DISTAL: RIGHT_THUMB_PROXIMAL, + RIGHT_INDEX_PROXIMAL: RIGHT_HAND, + RIGHT_INDEX_INTERMEDIATE: RIGHT_INDEX_PROXIMAL, + RIGHT_INDEX_DISTAL: RIGHT_INDEX_INTERMEDIATE, + RIGHT_MIDDLE_PROXIMAL: RIGHT_HAND, + RIGHT_MIDDLE_INTERMEDIATE: RIGHT_MIDDLE_PROXIMAL, + RIGHT_MIDDLE_DISTAL: RIGHT_MIDDLE_INTERMEDIATE, + RIGHT_RING_PROXIMAL: RIGHT_HAND, + RIGHT_RING_INTERMEDIATE: RIGHT_RING_PROXIMAL, + RIGHT_RING_DISTAL: RIGHT_RING_INTERMEDIATE, + RIGHT_LITTLE_PROXIMAL: RIGHT_HAND, + RIGHT_LITTLE_INTERMEDIATE: RIGHT_LITTLE_PROXIMAL, + RIGHT_LITTLE_DISTAL: RIGHT_LITTLE_INTERMEDIATE, +}; + +class VrmcVrmHumanoidHumanBones extends GltfProperty { + final Map bones; + + VrmcVrmHumanoidHumanBones._( + this.bones, + Map extensions, + Object extras) + : super(extensions, extras); + + static VrmcVrmHumanoidHumanBones fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_VRM_HUMANOID_HUMAN_BONES_MEMBERS, context); + } + + final bones = {}; + + for (final name in VRMC_VRM_HUMANOID_HUMAN_BONES_MEMBERS) { + bones[name] = getObjectFromInnerMap(map, name, context, + VrmcVrmHumanoidHumanBone.fromMap, + req: VRMC_VRM_HUMANOID_HUMAN_BONES_REQUIRED[name]); + } + + return VrmcVrmHumanoidHumanBones._( + bones, + getExtensions(map, VrmcVrmHumanoid, context), + getExtras(map, context)); + } + + void testHierarchy(Context context, String name) { + final boneNode = bones[name]?.node; + if (boneNode == null) { + return; + } + + // traverse for ancestor + var ancestorName = name; + VrmcVrmHumanoidHumanBone nearestAncestor; + + do { + ancestorName = VRMC_VRM_HUMANOID_HUMAN_BONES_PARENT_MAP[ancestorName]; + + if (ancestorName == null) { + // goes this path only when the target bone is hips + return; + } + + nearestAncestor = bones[ancestorName]; + + if (nearestAncestor != null) { + break; + } + + if (VRMC_VRM_HUMANOID_HUMAN_BONES_NEEDS_PARENT[name]) { + context.addIssue(LinkError.vrmcVrmInvalidHumanoidHierarchy, + name: name, args: [name, ancestorName]); + + return; + } + + if (VRMC_VRM_HUMANOID_HUMAN_BONES_REQUIRED[ancestorName]) { + // Required bone does not exist. + // An error for required bone should already be emitted, + // so we just ignore the dependency. + return; + } + } while (nearestAncestor == null); + + final parentNode = nearestAncestor.node; + + // check if the boneNode is a descendant of parentNode + var current = boneNode; + while (current != null) { + current = current.parent; + + if (current == parentNode) { + return; + } + } + + context.addIssue(LinkError.vrmcVrmInvalidHumanoidHierarchy, + name: name, args: [name, ancestorName]); + } + + @override + void link(Gltf gltf, Context context) { + bones.forEach((name, bone) { + bone?.link(gltf, context); + testHierarchy(context, name); + }); + } +} + +const String HUMAN_BONES = 'humanBones'; + +const List VRMC_VRM_HUMANOID_MEMBERS = [ + HUMAN_BONES, +]; + +class VrmcVrmHumanoid extends GltfProperty { + final VrmcVrmHumanoidHumanBones humanBones; + + VrmcVrmHumanoid._( + this.humanBones, Map extensions, Object extras) + : super(extensions, extras); + + static VrmcVrmHumanoid fromMap(Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_VRM_HUMANOID_MEMBERS, context); + } + + final humanBonesMap = getMap(map, HUMAN_BONES, context, req: true); + context.path.add(HUMAN_BONES); + final humanBones = + VrmcVrmHumanoidHumanBones.fromMap(humanBonesMap, context); + context.path.removeLast(); + + return VrmcVrmHumanoid._(humanBones, + getExtensions(map, VrmcVrmHumanoid, context), getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + humanBones?.link(gltf, context); + } +} diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm_meta.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm_meta.dart new file mode 100644 index 0000000..dda48e4 --- /dev/null +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm_meta.dart @@ -0,0 +1,226 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_meta; + +import 'package:gltf/src/base/gltf_property.dart'; + +const String NAME = 'name'; +const String VERSION = 'version'; +const String AUTHORS = 'authors'; +const String COPYRIGHT_INFORMATION = 'copyrightInformation'; +const String CONTACT_INFORMATION = 'contactInformation'; +const String REFERENCES = 'references'; +const String THIRD_PARTY_LICENSES = 'thirdPartyLicenses'; +const String THUMBNAIL_IMAGE = 'thumbnailImage'; +const String LICENSE_URL = 'licenseUrl'; +const String AVATAR_PERMISSION = 'avatarPermission'; +const String ALLOW_EXCESSIVELY_VIOLENT_USAGE = 'allowExcessivelyViolentUsage'; +const String ALLOW_EXCESSIVELY_SEXUAL_USAGE = 'allowExcessivelySexualUsage'; +const String ALLOW_POLITICAL_OR_RELIGIOUS_USAGE = + 'allowPoliticalOrReligiousUsage'; +const String ALLOW_ANTISOCIAL_OR_HATE_USAGE = 'allowAntisocialOrHateUsage'; +const String COMMERCIAL_USAGE = 'commercialUsage'; +const String CREDIT_NOTATION = 'creditNotation'; +const String ALLOW_REDISTRIBUTION = 'allowRedistribution'; +const String MODIFICATION = 'modification'; +const String OTHER_LICENSE_URL = 'otherLicenseUrl'; + +const List VRMC_VRM_META_MEMBERS = [ + NAME, + VERSION, + AUTHORS, + COPYRIGHT_INFORMATION, + CONTACT_INFORMATION, + REFERENCES, + THIRD_PARTY_LICENSES, + THUMBNAIL_IMAGE, + LICENSE_URL, + AVATAR_PERMISSION, + ALLOW_EXCESSIVELY_VIOLENT_USAGE, + ALLOW_EXCESSIVELY_SEXUAL_USAGE, + COMMERCIAL_USAGE, + ALLOW_POLITICAL_OR_RELIGIOUS_USAGE, + ALLOW_ANTISOCIAL_OR_HATE_USAGE, + CREDIT_NOTATION, + ALLOW_REDISTRIBUTION, + MODIFICATION, + OTHER_LICENSE_URL, +]; + +const String VRM_LICENSE_URL_10 = 'https://vrm.dev/licenses/1.0/'; + +const List VRMC_VRM_META_LICENSE_URLS = [ + VRM_LICENSE_URL_10, +]; + +const String ONLY_AUTHOR = 'onlyAuthor'; +const String ONLY_SEPARATELY_LICENSED_PERSON = 'onlySeparatelyLicensedPerson'; +const String EVERYONE = 'everyone'; + +const List VRMC_VRM_META_AVATAR_PERMISSIONS = [ + ONLY_AUTHOR, + ONLY_SEPARATELY_LICENSED_PERSON, + EVERYONE, +]; + +const String PERSONAL_NON_PROFIT = 'personalNonProfit'; +const String PERSONAL_PROFIT = 'personalProfit'; +const String CORPORATION = 'corporation'; + +const List VRMC_VRM_META_COMMERCIAL_USAGES = [ + PERSONAL_NON_PROFIT, + PERSONAL_PROFIT, + CORPORATION, +]; + +const String REQUIRED = 'required'; +const String UNNECESSARY = 'unnecessary'; + +const List VRMC_VRM_META_CREDIT_NOTATIONS = [ + REQUIRED, + UNNECESSARY, +]; + +const String PROHIBITED = 'prohibited'; +const String ALLOW_MODIFICATION = 'allowModification'; +const String ALLOW_MODIFICATION_REDISTRIBUTION = + 'allowModificationRedistribution'; + +const List VRMC_VRM_META_MODIFICATIONS = [ + PROHIBITED, + ALLOW_MODIFICATION, + ALLOW_MODIFICATION_REDISTRIBUTION, +]; + +class VrmcVrmMeta extends GltfProperty implements ResourceValidatable { + final String name; + final String version; + final List authors; + final String copyrightInformation; + final String contactInformation; + final List references; + final String thirdPartyLicenses; + final int thumbnailImageIndex; + final String licenseUrl; + final String avatarPermission; + final bool allowExcessivelyViolentUsage; + final bool allowExcessivelySexualUsage; + final String commercialUsage; + final bool allowPoliticalOrReligiousUsage; + final bool allowAntisocialOrHateUsage; + final String creditNotation; + final bool allowRedistribution; + final String modification; + final String otherLicenseUrl; + + Image thumbnailImage; + + VrmcVrmMeta._( + this.name, + this.version, + this.authors, + this.copyrightInformation, + this.contactInformation, + this.references, + this.thirdPartyLicenses, + this.thumbnailImageIndex, + this.licenseUrl, + this.avatarPermission, + this.allowExcessivelyViolentUsage, + this.allowExcessivelySexualUsage, + this.commercialUsage, + this.allowPoliticalOrReligiousUsage, + this.allowAntisocialOrHateUsage, + this.creditNotation, + this.allowRedistribution, + this.modification, + this.otherLicenseUrl, + Map extensions, + Object extras) + : super(extensions, extras); + + static VrmcVrmMeta fromMap(Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_VRM_META_MEMBERS, context); + } + + // length of authors must be 1 at least + // https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/meta.md#metaauthors- + final authors = getStringList(map, AUTHORS, context); + if (authors == null || authors.isEmpty) { + context.addIssue(SchemaError.undefinedProperty, + name: AUTHORS, args: [AUTHORS]); + } + + return VrmcVrmMeta._( + getString(map, NAME, context, req: true), + getString(map, VERSION, context, req: false), + authors, + getString(map, COPYRIGHT_INFORMATION, context, req: false), + getString(map, CONTACT_INFORMATION, context, req: false), + getStringList(map, REFERENCES, context), + getString(map, THIRD_PARTY_LICENSES, context, req: false), + getIndex(map, THUMBNAIL_IMAGE, context, req: false), + getString(map, LICENSE_URL, context, + list: VRMC_VRM_META_LICENSE_URLS, req: true), + getString(map, AVATAR_PERMISSION, context, + list: VRMC_VRM_META_AVATAR_PERMISSIONS, req: false), + getBool(map, ALLOW_EXCESSIVELY_VIOLENT_USAGE, context), + getBool(map, ALLOW_EXCESSIVELY_SEXUAL_USAGE, context), + getString(map, COMMERCIAL_USAGE, context, + list: VRMC_VRM_META_COMMERCIAL_USAGES, req: false), + getBool(map, ALLOW_POLITICAL_OR_RELIGIOUS_USAGE, context), + getBool(map, ALLOW_ANTISOCIAL_OR_HATE_USAGE, context), + getString(map, CREDIT_NOTATION, context, + list: VRMC_VRM_META_CREDIT_NOTATIONS, req: false), + getBool(map, ALLOW_REDISTRIBUTION, context), + getString(map, MODIFICATION, context, + list: VRMC_VRM_META_MODIFICATIONS, req: false), + getString(map, OTHER_LICENSE_URL, context, req: false), + getExtensions(map, VrmcVrmMeta, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + thumbnailImage = gltf.images[thumbnailImageIndex]; + if (context.validate && thumbnailImageIndex != -1) { + if (thumbnailImage == null) { + context.addIssue(LinkError.unresolvedReference, + name: THUMBNAIL_IMAGE, args: [thumbnailImageIndex]); + } else { + thumbnailImage.markAsUsed(); + } + } + } + + @override + void validateResources(Gltf gltf, Context context) { + // mimetype of thumbnail image, it must be either png or jpeg + // See: https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/meta.md#metathumbnailimage + final mimeType = thumbnailImage?.mimeType ?? thumbnailImage?.info?.mimeType; + if (mimeType != null && + !(mimeType != IMAGE_JPEG || mimeType == IMAGE_PNG)) { + context.addIssue(LinkError.textureInvalidImageMimeType, + name: THUMBNAIL_IMAGE, + args: [ + mimeType, + const [IMAGE_JPEG, IMAGE_PNG] + ]); + } + } +} diff --git a/lib/src/ext/extensions.dart b/lib/src/ext/extensions.dart index 4acdc04..f8f59f8 100644 --- a/lib/src/ext/extensions.dart +++ b/lib/src/ext/extensions.dart @@ -32,6 +32,7 @@ import 'package:gltf/src/ext/KHR_materials_variants/KHR_materials_variants.dart' 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_vrm/vrmc_vrm.dart'; import 'package:gltf/src/hash.dart'; import 'package:meta/meta.dart'; @@ -111,7 +112,8 @@ const List kDefaultExtensions = [ khrMaterialsVariantsExtension, khrMaterialsVolumeExtension, khrMeshQuantizationExtension, - khrTextureTransformExtension + khrTextureTransformExtension, + vrmcVrmExtension, ]; // https://github.com/KhronosGroup/glTF/blob/main/extensions/Prefixes.md From 3798bbbc0cd87c42834939b8a13c5b499e15a99a Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Mon, 1 Aug 2022 21:15:19 +0900 Subject: [PATCH 02/18] refactor: refactor vrmc_vrm_humanoid.dart - getMap + fromMap can replace by getObjectFromInnerMap - format --- lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart | 28 ++++++++------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart index cc78f72..e1368df 100644 --- a/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart @@ -349,9 +349,7 @@ class VrmcVrmHumanoidHumanBones extends GltfProperty { final Map bones; VrmcVrmHumanoidHumanBones._( - this.bones, - Map extensions, - Object extras) + this.bones, Map extensions, Object extras) : super(extensions, extras); static VrmcVrmHumanoidHumanBones fromMap( @@ -363,15 +361,13 @@ class VrmcVrmHumanoidHumanBones extends GltfProperty { final bones = {}; for (final name in VRMC_VRM_HUMANOID_HUMAN_BONES_MEMBERS) { - bones[name] = getObjectFromInnerMap(map, name, context, - VrmcVrmHumanoidHumanBone.fromMap, + bones[name] = getObjectFromInnerMap( + map, name, context, VrmcVrmHumanoidHumanBone.fromMap, req: VRMC_VRM_HUMANOID_HUMAN_BONES_REQUIRED[name]); } - return VrmcVrmHumanoidHumanBones._( - bones, - getExtensions(map, VrmcVrmHumanoid, context), - getExtras(map, context)); + return VrmcVrmHumanoidHumanBones._(bones, + getExtensions(map, VrmcVrmHumanoid, context), getExtras(map, context)); } void testHierarchy(Context context, String name) { @@ -456,14 +452,12 @@ class VrmcVrmHumanoid extends GltfProperty { checkMembers(map, VRMC_VRM_HUMANOID_MEMBERS, context); } - final humanBonesMap = getMap(map, HUMAN_BONES, context, req: true); - context.path.add(HUMAN_BONES); - final humanBones = - VrmcVrmHumanoidHumanBones.fromMap(humanBonesMap, context); - context.path.removeLast(); - - return VrmcVrmHumanoid._(humanBones, - getExtensions(map, VrmcVrmHumanoid, context), getExtras(map, context)); + return VrmcVrmHumanoid._( + getObjectFromInnerMap( + map, HUMAN_BONES, context, VrmcVrmHumanoidHumanBones.fromMap, + req: true), + getExtensions(map, VrmcVrmHumanoid, context), + getExtras(map, context)); } @override From f1feb1eb278a69934a78dfd64df8323024aea5a6 Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Mon, 1 Aug 2022 23:17:04 +0900 Subject: [PATCH 03/18] refactor: refactor vrmc_vrm_humanoid.dart - define NODE in the file - slight format --- lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart index e1368df..2b7ebd6 100644 --- a/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart @@ -18,6 +18,8 @@ library gltf.extensions.vrmc_vrm_humanoid; import 'package:gltf/src/base/gltf_property.dart'; +const NODE = 'node'; + const List VRMC_VRM_HUMANOID_HUMAN_BONE_MEMBERS = [ NODE, ]; @@ -37,9 +39,7 @@ class VrmcVrmHumanoidHumanBone extends GltfProperty { checkMembers(map, VRMC_VRM_HUMANOID_HUMAN_BONE_MEMBERS, context); } - final index = getIndex(map, NODE, context, req: true); - - return VrmcVrmHumanoidHumanBone._(index, + return VrmcVrmHumanoidHumanBone._(getIndex(map, NODE, context, req: true), getExtensions(map, VrmcVrmHumanoid, context), getExtras(map, context)); } From 789f1ce0c04ae5b6e8acc5bad72acf1fe573c374 Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Tue, 2 Aug 2022 00:35:46 +0900 Subject: [PATCH 04/18] wip feature: support VRMC_vrm.expressions --- lib/src/errors.dart | 27 + lib/src/ext/VRMC_vrm/vrmc_vrm.dart | 9 +- .../ext/VRMC_vrm/vrmc_vrm_expressions.dart | 534 ++++++++++++++++++ 3 files changed, 569 insertions(+), 1 deletion(-) create mode 100644 lib/src/ext/VRMC_vrm/vrmc_vrm_expressions.dart diff --git a/lib/src/errors.dart b/lib/src/errors.dart index 1a2ed0c..69235da 100644 --- a/lib/src/errors.dart +++ b/lib/src/errors.dart @@ -533,6 +533,21 @@ class SemanticError extends IssueType { 'minimum is equal to the thickness maximum.', Severity.Information); + static final SemanticError vrmcVrmExpressionsInvalidCustomExpression = + SemanticError._( + 'VRMC_VRM_EXPRESSIONS_INVALID_CUSTOM_EXPRESSION', + (args) => 'The expression "${_q(args[0])}" must be defined in preset ' + 'expressions and cannot be defined in custom expressions.'); + + static final SemanticError vrmcVrmExpressionsInvalidExpressionOverride = + SemanticError._( + 'VRMC_VRM_EXPRESSIONS_INVALID_EXPRESSION_OVERRIDE', + (args) => + 'The expression "${_q(args[0])}" has ' + 'the property ${_q(args[1])}, ' + 'which is ignored for this expression.', + Severity.Warning); + SemanticError._(String type, ErrorFunction message, [Severity severity = Severity.Error]) : super(type, message, severity); @@ -784,6 +799,18 @@ class LinkError extends IssueType { 'VRMC_VRM_INVALID_HUMANOID_HIERARCHY', (args) => '${_q(args[0])} must be a descendant of ${_q(args[1])}'); + static final LinkError vrmcVrmExpressionsNoTargetMorph = LinkError._( + 'VRMC_VRM_EXPRESSIONS_NO_TARGET_MORPH', + (args) => 'Node ${_q(args[0])} does not have the target morph ' + '${_q(args[1])}.'); + + static final LinkError vrmcVrmExpressionsIncompatibleMaterialBindType = + LinkError._( + 'VRMC_VRM_EXPRESSIONS_INCOMPATIBLE_MATERIAL_BIND_TYPE', + (args) => 'Material ${_q(args[0])} may not support ' + 'the material bind type "${_q(args[1])}".', + Severity.Warning); + LinkError._(String type, ErrorFunction message, [Severity severity = Severity.Error]) : super(type, message, severity); diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm.dart index 71810a1..1e79902 100644 --- a/lib/src/ext/VRMC_vrm/vrmc_vrm.dart +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm.dart @@ -17,6 +17,7 @@ library gltf.extensions.vrmc_vrm; import 'package:gltf/src/base/gltf_property.dart'; +import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm_expressions.dart'; import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart'; import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm_meta.dart'; import 'package:gltf/src/ext/extensions.dart'; @@ -25,11 +26,13 @@ const String VRMC_VRM = 'VRMC_vrm'; const String SPEC_VERSION = 'specVersion'; const String META = 'meta'; const String HUMANOID = 'humanoid'; +const String EXPRESSIONS = 'expressions'; const List VRMC_VRM_MEMBERS = [ SPEC_VERSION, META, HUMANOID, + EXPRESSIONS, ]; const String SPEC_VERSION_10_BETA = '1.0-beta'; @@ -42,8 +45,9 @@ class VrmcVrm extends GltfProperty { final String specVersion; final VrmcVrmMeta meta; final VrmcVrmHumanoid humanoid; + final VrmcVrmExpressions expressions; - VrmcVrm._(this.specVersion, this.meta, this.humanoid, + VrmcVrm._(this.specVersion, this.meta, this.humanoid, this.expressions, Map extensions, Object extras) : super(extensions, extras); @@ -61,6 +65,8 @@ class VrmcVrm extends GltfProperty { req: true), getObjectFromInnerMap(map, HUMANOID, context, VrmcVrmHumanoid.fromMap, req: true), + getObjectFromInnerMap(map, EXPRESSIONS, context, VrmcVrmExpressions.fromMap, + req: false), getExtensions(map, VrmcVrm, context), getExtras(map, context)); } @@ -69,6 +75,7 @@ class VrmcVrm extends GltfProperty { void link(Gltf gltf, Context context) { meta?.link(gltf, context); humanoid?.link(gltf, context); + expressions?.link(gltf, context); } } diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm_expressions.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm_expressions.dart new file mode 100644 index 0000000..b5bbc55 --- /dev/null +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm_expressions.dart @@ -0,0 +1,534 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_expressions; + +import 'package:gltf/src/base/gltf_property.dart'; +import 'package:gltf/src/ext/KHR_materials_unlit/khr_materials_unlit.dart'; + +// morphTargetBind +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/schema/VRMC_vrm.expressions.expression.morphTargetBind.schema.json + +const NODE = 'node'; +const INDEX = 'index'; +const WEIGHT = 'weight'; + +const List VRMC_VRM_EXPRESSIONS_MORPH_TARGET_BIND = [ + NODE, + INDEX, + WEIGHT, +]; + +class VrmcVrmExpressionsMorphTargetBind extends GltfProperty { + final int nodeIndex; + final int index; + final double weight; + + Node node; + + VrmcVrmExpressionsMorphTargetBind._(this.nodeIndex, this.index, this.weight, + Map extensions, Object extras) + : super(extensions, extras); + + static VrmcVrmExpressionsMorphTargetBind fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_VRM_EXPRESSIONS_MORPH_TARGET_BIND, context); + } + + return VrmcVrmExpressionsMorphTargetBind._( + getIndex(map, NODE, context, req: true), + getIndex(map, INDEX, context, req: true), + getFloat(map, WEIGHT, context, req: true), + getExtensions(map, VrmcVrmExpressionsMorphTargetBind, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + node = gltf.nodes[nodeIndex]; + + if (context.validate) { + // check node + if (nodeIndex != -1) { + if (node == null) { + context.addIssue(LinkError.unresolvedReference, + name: NODE, args: [nodeIndex]); + } else { + node.markAsUsed(); + } + } + + // check weight index + if (node != null) { + final targetsCount = node?.mesh?.primitives?.first?.targets?.length; + + if (targetsCount == null || targetsCount <= index) { + context.addIssue(LinkError.vrmcVrmExpressionsNoTargetMorph, + name: INDEX, args: [nodeIndex, index]); + } + } + } + } +} + +// materialColorBind +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/schema/VRMC_vrm.expressions.expression.materialColorBind.schema.json + +const MATERIAL = 'material'; +const TYPE = 'type'; +const TARGET_VALUE = 'targetValue'; + +const List VRMC_VRM_EXPRESSIONS_MATERIAL_COLOR_BIND = [ + MATERIAL, + TYPE, + TARGET_VALUE, +]; + +const COLOR = 'color'; +const EMISSION_COLOR = 'emissionColor'; +const SHADE_COLOR = 'shadeColor'; +const RIM_COLOR = 'rimColor'; +const OUTLINE_COLOR = 'outlineColor'; + +const List VRMC_VRM_EXPRESSIONS_MATERIAL_COLOR_BIND_TYPES = [ + COLOR, + EMISSION_COLOR, + SHADE_COLOR, + RIM_COLOR, + OUTLINE_COLOR, +]; + +const VRMC_MATERIALS_MTOON = 'VRMC_materials_mtoon'; + +class VrmcVrmExpressionsMaterialColorBind extends GltfProperty { + final int materialIndex; + final String type; + final List targetValue; + + Material material; + + VrmcVrmExpressionsMaterialColorBind._(this.materialIndex, this.type, + this.targetValue, Map extensions, Object extras) + : super(extensions, extras); + + static VrmcVrmExpressionsMaterialColorBind fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_VRM_EXPRESSIONS_MORPH_TARGET_BIND, context); + } + + return VrmcVrmExpressionsMaterialColorBind._( + getIndex(map, MATERIAL, context, req: true), + getString(map, TYPE, context, + list: VRMC_VRM_EXPRESSIONS_MATERIAL_COLOR_BIND_TYPES, req: true), + getFloatList(map, TARGET_VALUE, context, + req: true, lengthsList: const [4]), + getExtensions(map, VrmcVrmExpressionsMaterialColorBind, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + material = gltf.materials[materialIndex]; + + if (context.validate) { + // check material + if (materialIndex != -1) { + if (material == null) { + context.addIssue(LinkError.unresolvedReference, + name: MATERIAL, args: [materialIndex]); + } else { + material.markAsUsed(); + } + } + + // check type + if (material != null && type != null) { + if (material.extensions.containsKey(VRMC_MATERIALS_MTOON)) { + // MToon should support all types + } else if (material.extensions.containsKey(KHR_MATERIALS_UNLIT)) { + // KHR_materials_unlit only supports `color` + if (!(const [COLOR].contains(type))) { + context.addIssue( + LinkError.vrmcVrmExpressionsIncompatibleMaterialBindType, + name: TYPE, + args: [materialIndex, type]); + } + } else { + // It's probably PBR material + // PBR materials supports `color` and `emissionColor` + if (!(const [COLOR, EMISSION_COLOR].contains(type))) { + context.addIssue( + LinkError.vrmcVrmExpressionsIncompatibleMaterialBindType, + name: TYPE, + args: [materialIndex, type]); + } + } + } + } + } +} + +// textureTransformBind +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/schema/VRMC_vrm.expressions.expression.textureTransformBind.schema.json + +const SCALE = 'scale'; +const OFFSET = 'offset'; + +const List VRMC_VRM_EXPRESSIONS_TEXTURE_TRANSFORM_BIND = [ + MATERIAL, + SCALE, + OFFSET, +]; + +class VrmcVrmExpressionsTextureTransformBind extends GltfProperty { + final int materialIndex; + final List scale; + final List offset; + + Material material; + + VrmcVrmExpressionsTextureTransformBind._(this.materialIndex, this.scale, + this.offset, Map extensions, Object extras) + : super(extensions, extras); + + static VrmcVrmExpressionsTextureTransformBind fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_VRM_EXPRESSIONS_MORPH_TARGET_BIND, context); + } + + return VrmcVrmExpressionsTextureTransformBind._( + getIndex(map, MATERIAL, context, req: true), + getFloatList(map, SCALE, context, + lengthsList: const [2], def: const [1, 1]), + getFloatList(map, OFFSET, context, + lengthsList: const [2], def: const [0, 0]), + getExtensions(map, VrmcVrmExpressionsTextureTransformBind, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + material = gltf.materials[materialIndex]; + + if (context.validate) { + // check material + if (materialIndex != -1) { + if (material == null) { + context.addIssue(LinkError.unresolvedReference, + name: MATERIAL, args: [materialIndex]); + } else { + material.markAsUsed(); + } + } + } + } +} + +// expression +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/schema/VRMC_vrm.expressions.expression.schema.json + +const String MORPH_TARGET_BINDS = 'morphTargetBinds'; +const String MATERIAL_COLOR_BINDS = 'materialColorBinds'; +const String TEXTURE_TRANSFORM_BINDS = 'textureTransformBinds'; +const String IS_BINARY = 'isBinary'; +const String OVERRIDE_BLINK = 'overrideBlink'; +const String OVERRIDE_LOOK_AT = 'overrideLookAt'; +const String OVERRIDE_MOUTH = 'overrideMouth'; + +const List VRMC_VRM_EXPRESSIONS_EXPRESSION_MEMBERS = [ + MORPH_TARGET_BINDS, + MATERIAL_COLOR_BINDS, + TEXTURE_TRANSFORM_BINDS, + IS_BINARY, + OVERRIDE_BLINK, + OVERRIDE_LOOK_AT, + OVERRIDE_MOUTH, +]; + +const String NONE = 'none'; +const String BLOCK = 'block'; +const String BLEND = 'blend'; + +const List VRMC_VRM_EXPRESSIONS_OVERRIDES = [ + NONE, + BLOCK, + BLEND, +]; + +class VrmcVrmExpressionsExpression extends GltfProperty { + final List morphTargetBinds; + final List materialColorBinds; + final List textureTransformBinds; + final bool isBinary; + final String overrideBlink; + final String overrideLookAt; + final String overrideMouth; + + VrmcVrmExpressionsExpression._( + this.morphTargetBinds, + this.materialColorBinds, + this.textureTransformBinds, + this.isBinary, + this.overrideBlink, + this.overrideLookAt, + this.overrideMouth, + Map extensions, + Object extras) + : super(extensions, extras); + + static SafeList getObjectList(Map map, String name, + Context context, FromMapFunction fromMap) { + if (!map.containsKey(name)) { + return SafeList.empty(name); + } + + final list = getMapList(map, name, context); + final result = SafeList(list.length, name); + + context.path.add(name); + for (var i = 0; i < list.length; i++) { + final item = list[i]; + context.path.add(i.toString()); + result[i] = fromMap(item, context); + context.path.removeLast(); + } + context.path.removeLast(); + + return result; + } + + static VrmcVrmExpressionsExpression fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_VRM_EXPRESSIONS_EXPRESSION_MEMBERS, context); + } + + final morphTargetBinds = VrmcVrmExpressionsExpression.getObjectList< + VrmcVrmExpressionsMorphTargetBind>(map, MORPH_TARGET_BINDS, context, + VrmcVrmExpressionsMorphTargetBind.fromMap); + final materialColorBinds = VrmcVrmExpressionsExpression.getObjectList< + VrmcVrmExpressionsMaterialColorBind>(map, MATERIAL_COLOR_BINDS, + context, VrmcVrmExpressionsMaterialColorBind.fromMap); + final textureTransformBinds = VrmcVrmExpressionsExpression.getObjectList< + VrmcVrmExpressionsTextureTransformBind>( + map, + TEXTURE_TRANSFORM_BINDS, + context, + VrmcVrmExpressionsTextureTransformBind.fromMap); + + return VrmcVrmExpressionsExpression._( + morphTargetBinds, + materialColorBinds, + textureTransformBinds, + getBool(map, IS_BINARY, context), + getString(map, OVERRIDE_BLINK, context, + list: VRMC_VRM_EXPRESSIONS_OVERRIDES, req: false), + getString(map, OVERRIDE_LOOK_AT, context, + list: VRMC_VRM_EXPRESSIONS_OVERRIDES, req: false), + getString(map, OVERRIDE_MOUTH, context, + list: VRMC_VRM_EXPRESSIONS_OVERRIDES, req: false), + getExtensions(map, VrmcVrmExpressionsExpression, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + for (final bind in morphTargetBinds) { + bind.link(gltf, context); + } + + for (final bind in materialColorBinds) { + bind.link(gltf, context); + } + + for (final bind in textureTransformBinds) { + bind.link(gltf, context); + } + } + + void validateOverride(String name, Context context) { + // Override settings for the same kind are ignored + // https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/expressions.md#procedural-override + if (VRMC_VRM_EXPRESSIONS_BLINK_EXPRESSIONS.contains(name) && + (overrideBlink != null && overrideBlink != NONE)) { + context.addIssue( + SemanticError.vrmcVrmExpressionsInvalidExpressionOverride, + name: OVERRIDE_BLINK, + args: [name, OVERRIDE_BLINK]); + } + + if (VRMC_VRM_EXPRESSIONS_LOOK_AT_EXPRESSIONS.contains(name) && + (overrideLookAt != null && overrideLookAt != NONE)) { + context.addIssue( + SemanticError.vrmcVrmExpressionsInvalidExpressionOverride, + name: OVERRIDE_LOOK_AT, + args: [name, OVERRIDE_LOOK_AT]); + } + + if (VRMC_VRM_EXPRESSIONS_MOUTH_EXPRESSIONS.contains(name) && + (overrideMouth != null && overrideMouth != NONE)) { + context.addIssue( + SemanticError.vrmcVrmExpressionsInvalidExpressionOverride, + name: OVERRIDE_MOUTH, + args: [name, OVERRIDE_MOUTH]); + } + } +} + +// expressions +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/schema/VRMC_vrm.expressions.schema.json + +const HAPPY = 'happy'; +const ANGRY = 'angry'; +const SAD = 'sad'; +const RELAXED = 'relaxed'; +const SURPRISED = 'surprised'; +const AA = 'aa'; +const IH = 'ih'; +const OU = 'ou'; +const EE = 'ee'; +const OH = 'oh'; +const BLINK = 'blink'; +const BLINK_LEFT = 'blinkLeft'; +const BLINK_RIGHT = 'blinkRight'; +const LOOK_UP = 'lookUp'; +const LOOK_DOWN = 'lookDown'; +const LOOK_LEFT = 'lookLeft'; +const LOOK_RIGHT = 'lookRight'; +const NEUTRAL = 'neutral'; + +const List VRMC_VRM_EXPRESSIONS_PRESET_MEMBERS = [ + HAPPY, + ANGRY, + SAD, + RELAXED, + SURPRISED, + AA, + IH, + OU, + EE, + OH, + BLINK, + BLINK_LEFT, + BLINK_RIGHT, + LOOK_UP, + LOOK_DOWN, + LOOK_LEFT, + LOOK_RIGHT, + NEUTRAL, +]; + +const String PRESET = 'preset'; +const String CUSTOM = 'custom'; + +const List VRMC_VRM_EXPRESSIONS_MEMBERS = [ + PRESET, + CUSTOM, +]; + +const List VRMC_VRM_EXPRESSIONS_BLINK_EXPRESSIONS = [ + BLINK, + BLINK_LEFT, + BLINK_RIGHT, +]; + +const List VRMC_VRM_EXPRESSIONS_LOOK_AT_EXPRESSIONS = [ + LOOK_UP, + LOOK_DOWN, + LOOK_LEFT, + LOOK_RIGHT, +]; + +const List VRMC_VRM_EXPRESSIONS_MOUTH_EXPRESSIONS = [ + AA, + IH, + OU, + EE, + OH, +]; + +class VrmcVrmExpressions extends GltfProperty { + final Map preset; + final Map custom; + + VrmcVrmExpressions._( + this.preset, this.custom, Map extensions, Object extras) + : super(extensions, extras); + + static VrmcVrmExpressions fromMap(Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_VRM_EXPRESSIONS_MEMBERS, context); + } + + final preset = {}; + + final presetMap = getMap(map, PRESET, context, req: false); + if (presetMap != null) { + context.path.add(PRESET); + for (final name in presetMap.keys) { + checkMembers(presetMap, VRMC_VRM_EXPRESSIONS_PRESET_MEMBERS, context); + + preset[name] = getObjectFromInnerMap( + presetMap, name, context, VrmcVrmExpressionsExpression.fromMap); + } + context.path.removeLast(); + } + + final custom = {}; + + final customMap = getMap(map, CUSTOM, context, req: false); + if (customMap != null) { + context.path.add(CUSTOM); + for (final name in customMap.keys) { + // Custom Expressions cannot have names + // that are the same as any Preset Expressions + // https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/expressions.md#custom-expressions + if (VRMC_VRM_EXPRESSIONS_PRESET_MEMBERS.contains(name)) { + context.addIssue( + SemanticError.vrmcVrmExpressionsInvalidCustomExpression, + name: name, + args: [name]); + } + + custom[name] = getObjectFromInnerMap( + customMap, name, context, VrmcVrmExpressionsExpression.fromMap); + } + context.path.removeLast(); + } + + return VrmcVrmExpressions._( + preset, + custom, + getExtensions(map, VrmcVrmExpressions, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + preset.forEach((name, expression) { + expression + ..link(gltf, context) + ..validateOverride(name, context); + }); + + for (final expression in custom.values) { + expression.link(gltf, context); + } + } +} From 631aad47db8f4075ad31727647df0c570d370f12 Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Tue, 2 Aug 2022 00:37:14 +0900 Subject: [PATCH 05/18] change: reorganize VRM issue keys --- lib/src/errors.dart | 4 ++-- lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/errors.dart b/lib/src/errors.dart index 69235da..e441d01 100644 --- a/lib/src/errors.dart +++ b/lib/src/errors.dart @@ -795,8 +795,8 @@ class LinkError extends IssueType { 'KHR_MATERIALS_VARIANTS_NON_UNIQUE_VARIANT', (args) => 'This variant is used more than once for this mesh primitive.'); - static final LinkError vrmcVrmInvalidHumanoidHierarchy = LinkError._( - 'VRMC_VRM_INVALID_HUMANOID_HIERARCHY', + static final LinkError vrmcVrmHumanoidInvalidHierarchy = LinkError._( + 'VRMC_VRM_HUMANOID_INVALID_HIERARCHY', (args) => '${_q(args[0])} must be a descendant of ${_q(args[1])}'); static final LinkError vrmcVrmExpressionsNoTargetMorph = LinkError._( diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart index 2b7ebd6..640353c 100644 --- a/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart @@ -395,7 +395,7 @@ class VrmcVrmHumanoidHumanBones extends GltfProperty { } if (VRMC_VRM_HUMANOID_HUMAN_BONES_NEEDS_PARENT[name]) { - context.addIssue(LinkError.vrmcVrmInvalidHumanoidHierarchy, + context.addIssue(LinkError.vrmcVrmHumanoidInvalidHierarchy, name: name, args: [name, ancestorName]); return; @@ -421,7 +421,7 @@ class VrmcVrmHumanoidHumanBones extends GltfProperty { } } - context.addIssue(LinkError.vrmcVrmInvalidHumanoidHierarchy, + context.addIssue(LinkError.vrmcVrmHumanoidInvalidHierarchy, name: name, args: [name, ancestorName]); } From 7bb575e7a545f66094668f561e213194ec1f8ca3 Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Wed, 3 Aug 2022 21:32:18 +0900 Subject: [PATCH 06/18] wip feature: support VRMC_vrm.firstPerson --- lib/src/errors.dart | 9 +- lib/src/ext/VRMC_vrm/vrmc_vrm.dart | 12 +- .../ext/VRMC_vrm/vrmc_vrm_first_person.dart | 158 ++++++++++++++++++ 3 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 lib/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart diff --git a/lib/src/errors.dart b/lib/src/errors.dart index e441d01..e75255e 100644 --- a/lib/src/errors.dart +++ b/lib/src/errors.dart @@ -542,8 +542,7 @@ class SemanticError extends IssueType { static final SemanticError vrmcVrmExpressionsInvalidExpressionOverride = SemanticError._( 'VRMC_VRM_EXPRESSIONS_INVALID_EXPRESSION_OVERRIDE', - (args) => - 'The expression "${_q(args[0])}" has ' + (args) => 'The expression "${_q(args[0])}" has ' 'the property ${_q(args[1])}, ' 'which is ignored for this expression.', Severity.Warning); @@ -804,6 +803,12 @@ class LinkError extends IssueType { (args) => 'Node ${_q(args[0])} does not have the target morph ' '${_q(args[1])}.'); + static final LinkError vrmcVrmFirstPersonMeshAnnotationsNodeNotUnique = + LinkError._( + 'VRMC_VRM_FIRST_PERSON_MESH_ANNOTATIONS_NODE_NOT_UNIQUE', + (args) => 'Mesh annotations have duplicated node entries. ' + 'Duplicated nodes: ${args[0]}'); + static final LinkError vrmcVrmExpressionsIncompatibleMaterialBindType = LinkError._( 'VRMC_VRM_EXPRESSIONS_INCOMPATIBLE_MATERIAL_BIND_TYPE', diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm.dart index 1e79902..5c12e05 100644 --- a/lib/src/ext/VRMC_vrm/vrmc_vrm.dart +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm.dart @@ -18,6 +18,7 @@ library gltf.extensions.vrmc_vrm; import 'package:gltf/src/base/gltf_property.dart'; import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm_expressions.dart'; +import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart'; import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart'; import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm_meta.dart'; import 'package:gltf/src/ext/extensions.dart'; @@ -27,12 +28,14 @@ const String SPEC_VERSION = 'specVersion'; const String META = 'meta'; const String HUMANOID = 'humanoid'; const String EXPRESSIONS = 'expressions'; +const String FIRST_PERSON = 'firstPerson'; const List VRMC_VRM_MEMBERS = [ SPEC_VERSION, META, HUMANOID, EXPRESSIONS, + FIRST_PERSON, ]; const String SPEC_VERSION_10_BETA = '1.0-beta'; @@ -46,9 +49,10 @@ class VrmcVrm extends GltfProperty { final VrmcVrmMeta meta; final VrmcVrmHumanoid humanoid; final VrmcVrmExpressions expressions; + final VrmcVrmFirstPerson firstPerson; VrmcVrm._(this.specVersion, this.meta, this.humanoid, this.expressions, - Map extensions, Object extras) + this.firstPerson, Map extensions, Object extras) : super(extensions, extras); static VrmcVrm fromMap(Map map, Context context) { @@ -65,7 +69,10 @@ class VrmcVrm extends GltfProperty { req: true), getObjectFromInnerMap(map, HUMANOID, context, VrmcVrmHumanoid.fromMap, req: true), - getObjectFromInnerMap(map, EXPRESSIONS, context, VrmcVrmExpressions.fromMap, + getObjectFromInnerMap( + map, EXPRESSIONS, context, VrmcVrmExpressions.fromMap, req: false), + getObjectFromInnerMap( + map, FIRST_PERSON, context, VrmcVrmFirstPerson.fromMap, req: false), getExtensions(map, VrmcVrm, context), getExtras(map, context)); @@ -76,6 +83,7 @@ class VrmcVrm extends GltfProperty { meta?.link(gltf, context); humanoid?.link(gltf, context); expressions?.link(gltf, context); + firstPerson?.link(gltf, context); } } diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart new file mode 100644 index 0000000..276eab9 --- /dev/null +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart @@ -0,0 +1,158 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_expressions; + +import 'package:gltf/src/base/gltf_property.dart'; + +// meshAnnotation +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/schema/VRMC_vrm.firstPerson.meshAnnotation.schema.json + +const String NODE = 'node'; +const String TYPE = 'type'; + +const List VRMC_VRM_FIRST_PERSON_MESH_ANNOTATION_MEMBERS = [ + NODE, + TYPE, +]; + +const String AUTO = 'auto'; +const String BOTH = 'both'; +const String THIRD_PERSON_ONLY = 'thirdPersonOnly'; +const String FIRST_PERSON_ONLY = 'firstPersonOnly'; + +const List VRMC_VRM_FIRST_PERSON_MESH_ANNOTATION_TYPES = [ + AUTO, + BOTH, + THIRD_PERSON_ONLY, + FIRST_PERSON_ONLY, +]; + +class VrmcVrmFirstPersonMeshAnnotation extends GltfProperty { + final int nodeIndex; + final String type; + + Node node; + + VrmcVrmFirstPersonMeshAnnotation._( + this.nodeIndex, this.type, Map extensions, Object extras) + : super(extensions, extras); + + static VrmcVrmFirstPersonMeshAnnotation fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_VRM_FIRST_PERSON_MESH_ANNOTATION_MEMBERS, context); + } + + return VrmcVrmFirstPersonMeshAnnotation._( + getIndex(map, NODE, context, req: true), + getString(map, TYPE, context, + list: VRMC_VRM_FIRST_PERSON_MESH_ANNOTATION_TYPES, req: true), + getExtensions(map, VrmcVrmFirstPersonMeshAnnotation, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + node = gltf.nodes[nodeIndex]; + + if (context.validate) { + // check node + if (nodeIndex != -1) { + if (node == null) { + context.addIssue(LinkError.unresolvedReference, + name: NODE, args: [nodeIndex]); + } else { + node.markAsUsed(); + } + } + } + } +} + +// firstPerson +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/schema/VRMC_vrm.firstPerson.schema.json + +const String MESH_ANNOTATIONS = 'meshAnnotations'; + +const List VRMC_VRM_FIRST_PERSON_MEMBERS = [ + MESH_ANNOTATIONS, +]; + +class VrmcVrmFirstPerson extends GltfProperty { + final List meshAnnotations; + + VrmcVrmFirstPerson._( + this.meshAnnotations, Map extensions, Object extras) + : super(extensions, extras); + + static VrmcVrmFirstPerson fromMap(Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_VRM_FIRST_PERSON_MEMBERS, context); + } + + SafeList meshAnnotations; + + if (!map.containsKey(MESH_ANNOTATIONS)) { + meshAnnotations = + SafeList.empty(MESH_ANNOTATIONS); + } else { + final meshAnnotationMapList = getMapList(map, MESH_ANNOTATIONS, context); + meshAnnotations = SafeList( + meshAnnotationMapList.length, MESH_ANNOTATIONS); + + context.path.add(MESH_ANNOTATIONS); + for (var i = 0; i < meshAnnotationMapList.length; i++) { + final meshAnnotationMap = meshAnnotationMapList[i]; + context.path.add(i.toString()); + meshAnnotations[i] = VrmcVrmFirstPersonMeshAnnotation.fromMap( + meshAnnotationMap, context); + context.path.removeLast(); + } + context.path.removeLast(); + } + + return VrmcVrmFirstPerson._( + meshAnnotations, + getExtensions(map, VrmcVrmFirstPerson, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + for (final meshAnnotation in meshAnnotations) { + meshAnnotation.link(gltf, context); + } + + // check node index uniqueness + final duplicates = {}; + final foundSet = {}; + for (final meshAnnotation in meshAnnotations) { + final index = meshAnnotation.nodeIndex; + + // set.add returns true if the given item is not yet in the set, + //returns false if the item already exists + if (!foundSet.add(index)) { + duplicates.add(index); + } + } + + if (duplicates.isNotEmpty) { + context.addIssue(LinkError.vrmcVrmFirstPersonMeshAnnotationsNodeNotUnique, + name: MESH_ANNOTATIONS, args: [duplicates.join(', ')]); + } + } +} From c5da6ae3392cb52cdb8ec57227a9e49170ed7ebe Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Wed, 3 Aug 2022 21:34:37 +0900 Subject: [PATCH 07/18] change: review message format of vrm issues --- lib/src/errors.dart | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/src/errors.dart b/lib/src/errors.dart index e75255e..3f51e7a 100644 --- a/lib/src/errors.dart +++ b/lib/src/errors.dart @@ -536,13 +536,13 @@ class SemanticError extends IssueType { static final SemanticError vrmcVrmExpressionsInvalidCustomExpression = SemanticError._( 'VRMC_VRM_EXPRESSIONS_INVALID_CUSTOM_EXPRESSION', - (args) => 'The expression "${_q(args[0])}" must be defined in preset ' + (args) => 'The expression ${_q(args[0])} must be defined in preset ' 'expressions and cannot be defined in custom expressions.'); static final SemanticError vrmcVrmExpressionsInvalidExpressionOverride = SemanticError._( 'VRMC_VRM_EXPRESSIONS_INVALID_EXPRESSION_OVERRIDE', - (args) => 'The expression "${_q(args[0])}" has ' + (args) => 'The expression ${_q(args[0])} has ' 'the property ${_q(args[1])}, ' 'which is ignored for this expression.', Severity.Warning); @@ -796,12 +796,19 @@ class LinkError extends IssueType { static final LinkError vrmcVrmHumanoidInvalidHierarchy = LinkError._( 'VRMC_VRM_HUMANOID_INVALID_HIERARCHY', - (args) => '${_q(args[0])} must be a descendant of ${_q(args[1])}'); + (args) => '${_q(args[0])} must be a descendant of ${_q(args[1])}.'); static final LinkError vrmcVrmExpressionsNoTargetMorph = LinkError._( 'VRMC_VRM_EXPRESSIONS_NO_TARGET_MORPH', - (args) => 'Node ${_q(args[0])} does not have the target morph ' - '${_q(args[1])}.'); + (args) => 'Node ${args[0]} does not have the target morph ' + '${args[1]}.'); + + static final LinkError vrmcVrmExpressionsIncompatibleMaterialBindType = + LinkError._( + 'VRMC_VRM_EXPRESSIONS_INCOMPATIBLE_MATERIAL_BIND_TYPE', + (args) => 'Material ${args[0]} may not support ' + 'the material bind type ${_q(args[1])}.', + Severity.Warning); static final LinkError vrmcVrmFirstPersonMeshAnnotationsNodeNotUnique = LinkError._( @@ -809,13 +816,6 @@ class LinkError extends IssueType { (args) => 'Mesh annotations have duplicated node entries. ' 'Duplicated nodes: ${args[0]}'); - static final LinkError vrmcVrmExpressionsIncompatibleMaterialBindType = - LinkError._( - 'VRMC_VRM_EXPRESSIONS_INCOMPATIBLE_MATERIAL_BIND_TYPE', - (args) => 'Material ${_q(args[0])} may not support ' - 'the material bind type "${_q(args[1])}".', - Severity.Warning); - LinkError._(String type, ErrorFunction message, [Severity severity = Severity.Error]) : super(type, message, severity); From e216fdc6ea8bf51876cc4de28a0836b19bee4342 Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Wed, 3 Aug 2022 21:53:28 +0900 Subject: [PATCH 08/18] wip feature: vrm humanoid, validate duplicated bones of humanBones --- lib/src/errors.dart | 6 ++++ lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart | 33 +++++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/src/errors.dart b/lib/src/errors.dart index 3f51e7a..f57cf4f 100644 --- a/lib/src/errors.dart +++ b/lib/src/errors.dart @@ -533,6 +533,12 @@ class SemanticError extends IssueType { 'minimum is equal to the thickness maximum.', Severity.Information); + static final SemanticError vrmcVrmHumanoidHumanBonesNodeNotUnique = + SemanticError._( + 'VRMC_VRM_HUMANOID_HUMAN_BONES_NODE_NOT_UNIQUE', + (args) => 'Human bones have duplicated node entries. ' + 'Duplicated nodes: ${args[0]}'); + static final SemanticError vrmcVrmExpressionsInvalidCustomExpression = SemanticError._( 'VRMC_VRM_EXPRESSIONS_INVALID_CUSTOM_EXPRESSION', diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart index 640353c..7ff5e86 100644 --- a/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart @@ -352,6 +352,27 @@ class VrmcVrmHumanoidHumanBones extends GltfProperty { this.bones, Map extensions, Object extras) : super(extensions, extras); + static void validateDuplicates( + Map bones, Context context) { + // check node index uniqueness + final duplicates = {}; + final foundSet = {}; + for (final bone in bones.values) { + final index = bone.nodeIndex; + + // set.add returns true if the given item is not yet in the set, + // returns false if the item already exists + if (!foundSet.add(index)) { + duplicates.add(index); + } + } + + if (duplicates.isNotEmpty) { + context.addIssue(SemanticError.vrmcVrmHumanoidHumanBonesNodeNotUnique, + args: [duplicates.join(', ')]); + } + } + static VrmcVrmHumanoidHumanBones fromMap( Map map, Context context) { if (context.validate) { @@ -361,16 +382,22 @@ class VrmcVrmHumanoidHumanBones extends GltfProperty { final bones = {}; for (final name in VRMC_VRM_HUMANOID_HUMAN_BONES_MEMBERS) { - bones[name] = getObjectFromInnerMap( + final bone = getObjectFromInnerMap( map, name, context, VrmcVrmHumanoidHumanBone.fromMap, req: VRMC_VRM_HUMANOID_HUMAN_BONES_REQUIRED[name]); + + if (bone != null) { + bones[name] = bone; + } } + VrmcVrmHumanoidHumanBones.validateDuplicates(bones, context); + return VrmcVrmHumanoidHumanBones._(bones, getExtensions(map, VrmcVrmHumanoid, context), getExtras(map, context)); } - void testHierarchy(Context context, String name) { + void validateHierarchy(Context context, String name) { final boneNode = bones[name]?.node; if (boneNode == null) { return; @@ -429,7 +456,7 @@ class VrmcVrmHumanoidHumanBones extends GltfProperty { void link(Gltf gltf, Context context) { bones.forEach((name, bone) { bone?.link(gltf, context); - testHierarchy(context, name); + validateHierarchy(context, name); }); } } From a52104f6ab7fcc613ce520e1270e84d699ecdbd5 Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Wed, 3 Aug 2022 21:56:29 +0900 Subject: [PATCH 09/18] refactor: vrm firstPerson, refactor validateDuplicates it's now SemanticError, and static method --- lib/src/errors.dart | 12 +++--- .../ext/VRMC_vrm/vrmc_vrm_first_person.dart | 43 +++++++++++-------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/lib/src/errors.dart b/lib/src/errors.dart index f57cf4f..e2fc283 100644 --- a/lib/src/errors.dart +++ b/lib/src/errors.dart @@ -553,6 +553,12 @@ class SemanticError extends IssueType { 'which is ignored for this expression.', Severity.Warning); + static final SemanticError vrmcVrmFirstPersonMeshAnnotationsNodeNotUnique = + SemanticError._( + 'VRMC_VRM_FIRST_PERSON_MESH_ANNOTATIONS_NODE_NOT_UNIQUE', + (args) => 'Mesh annotations have duplicated node entries. ' + 'Duplicated nodes: ${args[0]}'); + SemanticError._(String type, ErrorFunction message, [Severity severity = Severity.Error]) : super(type, message, severity); @@ -816,12 +822,6 @@ class LinkError extends IssueType { 'the material bind type ${_q(args[1])}.', Severity.Warning); - static final LinkError vrmcVrmFirstPersonMeshAnnotationsNodeNotUnique = - LinkError._( - 'VRMC_VRM_FIRST_PERSON_MESH_ANNOTATIONS_NODE_NOT_UNIQUE', - (args) => 'Mesh annotations have duplicated node entries. ' - 'Duplicated nodes: ${args[0]}'); - LinkError._(String type, ErrorFunction message, [Severity severity = Severity.Error]) : super(type, message, severity); diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart index 276eab9..b545fc0 100644 --- a/lib/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart @@ -99,6 +99,29 @@ class VrmcVrmFirstPerson extends GltfProperty { this.meshAnnotations, Map extensions, Object extras) : super(extensions, extras); + static void validateDuplicates( + List meshAnnotations, Context context) { + // check node index uniqueness + final duplicates = {}; + final foundSet = {}; + for (final meshAnnotation in meshAnnotations) { + final index = meshAnnotation.nodeIndex; + + // set.add returns true if the given item is not yet in the set, + // returns false if the item already exists + if (!foundSet.add(index)) { + duplicates.add(index); + } + } + + if (duplicates.isNotEmpty) { + context.addIssue( + SemanticError.vrmcVrmFirstPersonMeshAnnotationsNodeNotUnique, + name: MESH_ANNOTATIONS, + args: [duplicates.join(', ')]); + } + } + static VrmcVrmFirstPerson fromMap(Map map, Context context) { if (context.validate) { checkMembers(map, VRMC_VRM_FIRST_PERSON_MEMBERS, context); @@ -125,6 +148,8 @@ class VrmcVrmFirstPerson extends GltfProperty { context.path.removeLast(); } + VrmcVrmFirstPerson.validateDuplicates(meshAnnotations, context); + return VrmcVrmFirstPerson._( meshAnnotations, getExtensions(map, VrmcVrmFirstPerson, context), @@ -136,23 +161,5 @@ class VrmcVrmFirstPerson extends GltfProperty { for (final meshAnnotation in meshAnnotations) { meshAnnotation.link(gltf, context); } - - // check node index uniqueness - final duplicates = {}; - final foundSet = {}; - for (final meshAnnotation in meshAnnotations) { - final index = meshAnnotation.nodeIndex; - - // set.add returns true if the given item is not yet in the set, - //returns false if the item already exists - if (!foundSet.add(index)) { - duplicates.add(index); - } - } - - if (duplicates.isNotEmpty) { - context.addIssue(LinkError.vrmcVrmFirstPersonMeshAnnotationsNodeNotUnique, - name: MESH_ANNOTATIONS, args: [duplicates.join(', ')]); - } } } From c6b3a4641ae1a35fddac925b4322761ba88dd170 Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Wed, 3 Aug 2022 22:28:27 +0900 Subject: [PATCH 10/18] change: vrm humanoid and firstPerson, duplicate check now points at more precise position --- lib/src/errors.dart | 22 ++++--- .../ext/VRMC_vrm/vrmc_vrm_first_person.dart | 58 +++++++++++-------- lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart | 52 +++++++++-------- 3 files changed, 72 insertions(+), 60 deletions(-) diff --git a/lib/src/errors.dart b/lib/src/errors.dart index e2fc283..d7aa7b9 100644 --- a/lib/src/errors.dart +++ b/lib/src/errors.dart @@ -533,12 +533,6 @@ class SemanticError extends IssueType { 'minimum is equal to the thickness maximum.', Severity.Information); - static final SemanticError vrmcVrmHumanoidHumanBonesNodeNotUnique = - SemanticError._( - 'VRMC_VRM_HUMANOID_HUMAN_BONES_NODE_NOT_UNIQUE', - (args) => 'Human bones have duplicated node entries. ' - 'Duplicated nodes: ${args[0]}'); - static final SemanticError vrmcVrmExpressionsInvalidCustomExpression = SemanticError._( 'VRMC_VRM_EXPRESSIONS_INVALID_CUSTOM_EXPRESSION', @@ -553,12 +547,6 @@ class SemanticError extends IssueType { 'which is ignored for this expression.', Severity.Warning); - static final SemanticError vrmcVrmFirstPersonMeshAnnotationsNodeNotUnique = - SemanticError._( - 'VRMC_VRM_FIRST_PERSON_MESH_ANNOTATIONS_NODE_NOT_UNIQUE', - (args) => 'Mesh annotations have duplicated node entries. ' - 'Duplicated nodes: ${args[0]}'); - SemanticError._(String type, ErrorFunction message, [Severity severity = Severity.Error]) : super(type, message, severity); @@ -806,6 +794,11 @@ class LinkError extends IssueType { 'KHR_MATERIALS_VARIANTS_NON_UNIQUE_VARIANT', (args) => 'This variant is used more than once for this mesh primitive.'); + static final LinkError vrmcVrmHumanoidHumanBoneOverride = + LinkError._( + 'VRMC_VRM_HUMANOID_HUMAN_BONE_OVERRIDE', + (args) => 'Value overrides human bone of node ${args[0]}.'); + static final LinkError vrmcVrmHumanoidInvalidHierarchy = LinkError._( 'VRMC_VRM_HUMANOID_INVALID_HIERARCHY', (args) => '${_q(args[0])} must be a descendant of ${_q(args[1])}.'); @@ -822,6 +815,11 @@ class LinkError extends IssueType { 'the material bind type ${_q(args[1])}.', Severity.Warning); + static final LinkError vrmcVrmFirstPersonMeshAnnotationOverride = + LinkError._( + 'VRMC_VRM_FIRST_PERSON_MESH_ANNOTATION_OVERRIDE', + (args) => 'Value overrides mesh annotation of node ${args[0]}.'); + LinkError._(String type, ErrorFunction message, [Severity severity = Severity.Error]) : super(type, message, severity); diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart index b545fc0..f98a1ca 100644 --- a/lib/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart @@ -17,6 +17,7 @@ library gltf.extensions.vrmc_vrm_expressions; import 'package:gltf/src/base/gltf_property.dart'; +import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm.dart'; // meshAnnotation // https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/schema/VRMC_vrm.firstPerson.meshAnnotation.schema.json @@ -99,29 +100,6 @@ class VrmcVrmFirstPerson extends GltfProperty { this.meshAnnotations, Map extensions, Object extras) : super(extensions, extras); - static void validateDuplicates( - List meshAnnotations, Context context) { - // check node index uniqueness - final duplicates = {}; - final foundSet = {}; - for (final meshAnnotation in meshAnnotations) { - final index = meshAnnotation.nodeIndex; - - // set.add returns true if the given item is not yet in the set, - // returns false if the item already exists - if (!foundSet.add(index)) { - duplicates.add(index); - } - } - - if (duplicates.isNotEmpty) { - context.addIssue( - SemanticError.vrmcVrmFirstPersonMeshAnnotationsNodeNotUnique, - name: MESH_ANNOTATIONS, - args: [duplicates.join(', ')]); - } - } - static VrmcVrmFirstPerson fromMap(Map map, Context context) { if (context.validate) { checkMembers(map, VRMC_VRM_FIRST_PERSON_MEMBERS, context); @@ -148,18 +126,48 @@ class VrmcVrmFirstPerson extends GltfProperty { context.path.removeLast(); } - VrmcVrmFirstPerson.validateDuplicates(meshAnnotations, context); - return VrmcVrmFirstPerson._( meshAnnotations, getExtensions(map, VrmcVrmFirstPerson, context), getExtras(map, context)); } + void validateDuplicates(Context context) { + // check node index uniqueness + final foundSet = {}; + + context.path.add(MESH_ANNOTATIONS); + for (var i = 0; i < meshAnnotations.length; i ++) { + context.path.add(i.toString()); + + final meshAnnotation = meshAnnotations[i]; + final index = meshAnnotation.nodeIndex; + + context.path.add(NODE); + + // set.add returns true if the given item is not yet in the set, + // returns false if the item already exists + if (!foundSet.add(index)) { + context.addIssue( + LinkError.vrmcVrmFirstPersonMeshAnnotationOverride, + args: [index]); + } + + context.path.removeLast(); + + context.path.removeLast(); + } + context.path.removeLast(); + } + @override void link(Gltf gltf, Context context) { + validateDuplicates(context); + + context.path.add(MESH_ANNOTATIONS); for (final meshAnnotation in meshAnnotations) { meshAnnotation.link(gltf, context); } + context.path.removeLast(); } } diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart index 7ff5e86..502d0a5 100644 --- a/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart @@ -352,27 +352,6 @@ class VrmcVrmHumanoidHumanBones extends GltfProperty { this.bones, Map extensions, Object extras) : super(extensions, extras); - static void validateDuplicates( - Map bones, Context context) { - // check node index uniqueness - final duplicates = {}; - final foundSet = {}; - for (final bone in bones.values) { - final index = bone.nodeIndex; - - // set.add returns true if the given item is not yet in the set, - // returns false if the item already exists - if (!foundSet.add(index)) { - duplicates.add(index); - } - } - - if (duplicates.isNotEmpty) { - context.addIssue(SemanticError.vrmcVrmHumanoidHumanBonesNodeNotUnique, - args: [duplicates.join(', ')]); - } - } - static VrmcVrmHumanoidHumanBones fromMap( Map map, Context context) { if (context.validate) { @@ -391,12 +370,35 @@ class VrmcVrmHumanoidHumanBones extends GltfProperty { } } - VrmcVrmHumanoidHumanBones.validateDuplicates(bones, context); - return VrmcVrmHumanoidHumanBones._(bones, getExtensions(map, VrmcVrmHumanoid, context), getExtras(map, context)); } + void validateDuplicates(Context context) { + // check node index uniqueness + final foundSet = {}; + + bones.forEach((name, bone) { + context.path.add(name); + + final index = bone.nodeIndex; + + context.path.add(NODE); + + // set.add returns true if the given item is not yet in the set, + // returns false if the item already exists + if (!foundSet.add(index)) { + context.addIssue( + LinkError.vrmcVrmHumanoidHumanBoneOverride, + args: [index]); + } + + context.path.removeLast(); + + context.path.removeLast(); + }); + } + void validateHierarchy(Context context, String name) { final boneNode = bones[name]?.node; if (boneNode == null) { @@ -454,6 +456,8 @@ class VrmcVrmHumanoidHumanBones extends GltfProperty { @override void link(Gltf gltf, Context context) { + validateDuplicates(context); + bones.forEach((name, bone) { bone?.link(gltf, context); validateHierarchy(context, name); @@ -489,6 +493,8 @@ class VrmcVrmHumanoid extends GltfProperty { @override void link(Gltf gltf, Context context) { + context.path.add(HUMAN_BONES); humanBones?.link(gltf, context); + context.path.removeLast(); } } From 3c6a06c7ad0e66f5c351d7e82d7597b0f90b0615 Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Wed, 3 Aug 2022 22:35:06 +0900 Subject: [PATCH 11/18] fix: fix vrm context path --- lib/src/ext/VRMC_vrm/vrmc_vrm.dart | 11 ++++++ .../ext/VRMC_vrm/vrmc_vrm_expressions.dart | 36 ++++++++++++++----- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm.dart index 5c12e05..e4af660 100644 --- a/lib/src/ext/VRMC_vrm/vrmc_vrm.dart +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm.dart @@ -80,10 +80,21 @@ class VrmcVrm extends GltfProperty { @override void link(Gltf gltf, Context context) { + context.path.add(META); meta?.link(gltf, context); + context.path.removeLast(); + + context.path.add(HUMANOID); humanoid?.link(gltf, context); + context.path.removeLast(); + + context.path.add(EXPRESSIONS); expressions?.link(gltf, context); + context.path.removeLast(); + + context.path.add(FIRST_PERSON); firstPerson?.link(gltf, context); + context.path.removeLast(); } } diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm_expressions.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm_expressions.dart index b5bbc55..93b7f19 100644 --- a/lib/src/ext/VRMC_vrm/vrmc_vrm_expressions.dart +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm_expressions.dart @@ -349,17 +349,29 @@ class VrmcVrmExpressionsExpression extends GltfProperty { @override void link(Gltf gltf, Context context) { - for (final bind in morphTargetBinds) { - bind.link(gltf, context); + context.path.add(MORPH_TARGET_BINDS); + for (var i = 0; i < morphTargetBinds.length; i ++) { + context.path.add(i.toString()); + morphTargetBinds[i].link(gltf, context); + context.path.removeLast(); } + context.path.removeLast(); - for (final bind in materialColorBinds) { - bind.link(gltf, context); + context.path.add(MATERIAL_COLOR_BINDS); + for (var i = 0; i < materialColorBinds.length; i ++) { + context.path.add(i.toString()); + materialColorBinds[i].link(gltf, context); + context.path.removeLast(); } + context.path.removeLast(); - for (final bind in textureTransformBinds) { - bind.link(gltf, context); + context.path.add(TEXTURE_TRANSFORM_BINDS); + for (var i = 0; i < textureTransformBinds.length; i ++) { + context.path.add(i.toString()); + textureTransformBinds[i].link(gltf, context); + context.path.removeLast(); } + context.path.removeLast(); } void validateOverride(String name, Context context) { @@ -521,14 +533,22 @@ class VrmcVrmExpressions extends GltfProperty { @override void link(Gltf gltf, Context context) { + context.path.add(PRESET); preset.forEach((name, expression) { + context.path.add(name); expression ..link(gltf, context) ..validateOverride(name, context); + context.path.removeLast(); }); + context.path.removeLast(); - for (final expression in custom.values) { + context.path.add(CUSTOM); + custom.forEach((name, expression) { + context.path.add(name); expression.link(gltf, context); - } + context.path.removeLast(); + }); + context.path.removeLast(); } } From 4dc80ffbc7dddec2b35bb870993e180b3b503364 Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Thu, 18 Aug 2022 23:57:14 +0900 Subject: [PATCH 12/18] feature: support VRMC_vrm this should be a full features of VRMC_vrm base extension --- lib/src/errors.dart | 7 + lib/src/ext/VRMC_vrm/vrmc_vrm.dart | 24 ++- lib/src/ext/VRMC_vrm/vrmc_vrm_look_at.dart | 179 +++++++++++++++++++++ 3 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 lib/src/ext/VRMC_vrm/vrmc_vrm_look_at.dart diff --git a/lib/src/errors.dart b/lib/src/errors.dart index d7aa7b9..68c8df7 100644 --- a/lib/src/errors.dart +++ b/lib/src/errors.dart @@ -820,6 +820,13 @@ class LinkError extends IssueType { 'VRMC_VRM_FIRST_PERSON_MESH_ANNOTATION_OVERRIDE', (args) => 'Value overrides mesh annotation of node ${args[0]}.'); + static final LinkError vrmcVrmLookAtTypeNoEffect = + LinkError._( + 'VRMC_VRM_LOOK_AT_TYPE_NO_EFFECT', + (args) => 'LookAt type is ${args[0]} but there are no corresponding ' + '${args[1]} available.', + Severity.Information); + LinkError._(String type, ErrorFunction message, [Severity severity = Severity.Error]) : super(type, message, severity); diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm.dart index e4af660..43fc894 100644 --- a/lib/src/ext/VRMC_vrm/vrmc_vrm.dart +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm.dart @@ -20,6 +20,7 @@ import 'package:gltf/src/base/gltf_property.dart'; import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm_expressions.dart'; import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart'; import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart'; +import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm_look_at.dart'; import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm_meta.dart'; import 'package:gltf/src/ext/extensions.dart'; @@ -29,6 +30,7 @@ const String META = 'meta'; const String HUMANOID = 'humanoid'; const String EXPRESSIONS = 'expressions'; const String FIRST_PERSON = 'firstPerson'; +const String LOOK_AT = 'lookAt'; const List VRMC_VRM_MEMBERS = [ SPEC_VERSION, @@ -36,6 +38,7 @@ const List VRMC_VRM_MEMBERS = [ HUMANOID, EXPRESSIONS, FIRST_PERSON, + LOOK_AT, ]; const String SPEC_VERSION_10_BETA = '1.0-beta'; @@ -50,9 +53,17 @@ class VrmcVrm extends GltfProperty { final VrmcVrmHumanoid humanoid; final VrmcVrmExpressions expressions; final VrmcVrmFirstPerson firstPerson; - - VrmcVrm._(this.specVersion, this.meta, this.humanoid, this.expressions, - this.firstPerson, Map extensions, Object extras) + final VrmcVrmLookAt lookAt; + + VrmcVrm._( + this.specVersion, + this.meta, + this.humanoid, + this.expressions, + this.firstPerson, + this.lookAt, + Map extensions, + Object extras) : super(extensions, extras); static VrmcVrm fromMap(Map map, Context context) { @@ -72,7 +83,8 @@ class VrmcVrm extends GltfProperty { getObjectFromInnerMap( map, EXPRESSIONS, context, VrmcVrmExpressions.fromMap, req: false), getObjectFromInnerMap( - map, FIRST_PERSON, context, VrmcVrmFirstPerson.fromMap, + map, FIRST_PERSON, context, VrmcVrmFirstPerson.fromMap, req: false), + getObjectFromInnerMap(map, LOOK_AT, context, VrmcVrmLookAt.fromMap, req: false), getExtensions(map, VrmcVrm, context), getExtras(map, context)); @@ -95,6 +107,10 @@ class VrmcVrm extends GltfProperty { context.path.add(FIRST_PERSON); firstPerson?.link(gltf, context); context.path.removeLast(); + + context.path.add(LOOK_AT); + lookAt?.link(gltf, context); + context.path.removeLast(); } } diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm_look_at.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm_look_at.dart new file mode 100644 index 0000000..c98664c --- /dev/null +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm_look_at.dart @@ -0,0 +1,179 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_expressions; + +import 'package:gltf/src/base/gltf_property.dart'; +import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm.dart'; +import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm_expressions.dart'; +import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart'; +import 'package:vector_math/vector_math.dart'; + +// rangeMap +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/schema/VRMC_vrm.lookAt.rangeMap.schema.json + +const String INPUT_MAX_VALUE = 'inputMaxValue'; +const String OUTPUT_SCALE = 'outputScale'; + +const List VRMC_VRM_LOOK_AT_RANGE_MAP_MEMBERS = [ + INPUT_MAX_VALUE, + OUTPUT_SCALE, +]; + +class VrmcVrmLookAtRangeMap extends GltfProperty { + final double inputMaxValue; + final double outputScale; + + Node node; + + VrmcVrmLookAtRangeMap._(this.inputMaxValue, this.outputScale, + Map extensions, Object extras) + : super(extensions, extras); + + static VrmcVrmLookAtRangeMap fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_VRM_LOOK_AT_RANGE_MAP_MEMBERS, context); + } + + return VrmcVrmLookAtRangeMap._( + getFloat(map, INPUT_MAX_VALUE, context), + getFloat(map, OUTPUT_SCALE, context), + getExtensions(map, VrmcVrmLookAtRangeMap, context), + getExtras(map, context)); + } +} + +// lookAt +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/schema/VRMC_vrm.lookAt.schema.json + +const String OFFSET_FROM_HEAD_BONE = 'offsetFromHeadBone'; +const String TYPE = 'type'; +const String RANGE_MAP_HORIZONTAL_INNER = 'rangeMapHorizontalInner'; +const String RANGE_MAP_HORIZONTAL_OUTER = 'rangeMapHorizontalOuter'; +const String RANGE_MAP_VERTICAL_DOWN = 'rangeMapVerticalDown'; +const String RANGE_MAP_VERTICAL_UP = 'rangeMapVerticalUp'; + +const List VRMC_VRM_FIRST_PERSON_MEMBERS = [ + OFFSET_FROM_HEAD_BONE, + TYPE, + RANGE_MAP_HORIZONTAL_INNER, + RANGE_MAP_HORIZONTAL_OUTER, + RANGE_MAP_VERTICAL_DOWN, + RANGE_MAP_VERTICAL_UP, +]; + +const BONE = 'bone'; +const EXPRESSION = 'expression'; + +const List VRMC_VRM_LOOK_AT_TYPES = [ + BONE, + EXPRESSION, +]; + +class VrmcVrmLookAt extends GltfProperty { + final Vector3 offsetFromHeadBone; + final String type; + final VrmcVrmLookAtRangeMap rangeMapHorizontalInner; + final VrmcVrmLookAtRangeMap rangeMapHorizontalOuter; + final VrmcVrmLookAtRangeMap rangeMapVerticalDown; + final VrmcVrmLookAtRangeMap rangeMapVerticalUp; + + VrmcVrmLookAt._( + this.offsetFromHeadBone, + this.type, + this.rangeMapHorizontalInner, + this.rangeMapHorizontalOuter, + this.rangeMapVerticalDown, + this.rangeMapVerticalUp, + Map extensions, + Object extras) + : super(extensions, extras); + + static VrmcVrmLookAt fromMap(Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_VRM_FIRST_PERSON_MEMBERS, context); + } + + Vector3 offsetFromHeadBone; + if (map.containsKey(OFFSET_FROM_HEAD_BONE)) { + final list = getFloatList(map, OFFSET_FROM_HEAD_BONE, context, + lengthsList: const [3]); + if (list != null) { + offsetFromHeadBone = Vector3.array(list); + } + } + + return VrmcVrmLookAt._( + offsetFromHeadBone, + getString(map, TYPE, context, list: VRMC_VRM_LOOK_AT_TYPES), + getObjectFromInnerMap(map, RANGE_MAP_HORIZONTAL_INNER, context, + VrmcVrmLookAtRangeMap.fromMap), + getObjectFromInnerMap(map, RANGE_MAP_HORIZONTAL_OUTER, context, + VrmcVrmLookAtRangeMap.fromMap), + getObjectFromInnerMap(map, RANGE_MAP_VERTICAL_DOWN, context, + VrmcVrmLookAtRangeMap.fromMap), + getObjectFromInnerMap( + map, RANGE_MAP_VERTICAL_UP, context, VrmcVrmLookAtRangeMap.fromMap), + getExtensions(map, VrmcVrmLookAt, context), + getExtras(map, context)); + } + + void validateType(Gltf gltf, Context context) { + context.path.add(TYPE); + + if (type != null) { + final vrmExtension = gltf.extensions[VRMC_VRM]; + if (vrmExtension is VrmcVrm) { + if (type == BONE) { + // if type is bone and humanoid does not have eyes, + // emit a warning + + final bones = vrmExtension.humanoid.humanBones.bones; + + if (bones.containsKey(LEFT_EYE) || bones.containsKey(RIGHT_EYE)) { + // ok! + } else { + context.addIssue(LinkError.vrmcVrmLookAtTypeNoEffect, + args: [BONE, 'bones']); + } + } else if (type == EXPRESSION) { + // if type is expression and expression does not have eye expressions, + // emit a warning + + final presets = vrmExtension.expressions.preset; + + if (presets.containsKey(LOOK_LEFT) || + presets.containsKey(LOOK_RIGHT) || + presets.containsKey(LOOK_DOWN) || + presets.containsKey(LOOK_UP)) { + // ok! + } else { + context.addIssue(LinkError.vrmcVrmLookAtTypeNoEffect, + args: [EXPRESSION, 'expressions']); + } + } + } + } + + context.path.removeLast(); + } + + @override + void link(Gltf gltf, Context context) { + validateType(gltf, context); + } +} From 906176216c59ade77b8d75f9db24edaa89c79f68 Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Fri, 19 Aug 2022 20:11:15 +0900 Subject: [PATCH 13/18] fix: fix context path of VRM first person indices of meshAnnotations was not applied to the path --- lib/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart index f98a1ca..97542a9 100644 --- a/lib/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart @@ -17,7 +17,6 @@ library gltf.extensions.vrmc_vrm_expressions; import 'package:gltf/src/base/gltf_property.dart'; -import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm.dart'; // meshAnnotation // https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0-beta/schema/VRMC_vrm.firstPerson.meshAnnotation.schema.json @@ -165,8 +164,10 @@ class VrmcVrmFirstPerson extends GltfProperty { validateDuplicates(context); context.path.add(MESH_ANNOTATIONS); - for (final meshAnnotation in meshAnnotations) { - meshAnnotation.link(gltf, context); + for (var i = 0; i < meshAnnotations.length; i++) { + context.path.add(i.toString()); + meshAnnotations[i].link(gltf, context); + context.path.removeLast(); } context.path.removeLast(); } From dfde8f415f71d9e1c134c4b3866b024dee2b6ff1 Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Fri, 19 Aug 2022 20:11:47 +0900 Subject: [PATCH 14/18] refactor: slight refactor --- .../ext/VRMC_vrm/vrmc_vrm_expressions.dart | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/src/ext/VRMC_vrm/vrmc_vrm_expressions.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm_expressions.dart index 93b7f19..9f3539b 100644 --- a/lib/src/ext/VRMC_vrm/vrmc_vrm_expressions.dart +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm_expressions.dart @@ -319,14 +319,14 @@ class VrmcVrmExpressionsExpression extends GltfProperty { checkMembers(map, VRMC_VRM_EXPRESSIONS_EXPRESSION_MEMBERS, context); } - final morphTargetBinds = VrmcVrmExpressionsExpression.getObjectList< - VrmcVrmExpressionsMorphTargetBind>(map, MORPH_TARGET_BINDS, context, - VrmcVrmExpressionsMorphTargetBind.fromMap); - final materialColorBinds = VrmcVrmExpressionsExpression.getObjectList< - VrmcVrmExpressionsMaterialColorBind>(map, MATERIAL_COLOR_BINDS, - context, VrmcVrmExpressionsMaterialColorBind.fromMap); - final textureTransformBinds = VrmcVrmExpressionsExpression.getObjectList< - VrmcVrmExpressionsTextureTransformBind>( + final morphTargetBinds = VrmcVrmExpressionsExpression.getObjectList(map, + MORPH_TARGET_BINDS, context, VrmcVrmExpressionsMorphTargetBind.fromMap); + final materialColorBinds = VrmcVrmExpressionsExpression.getObjectList( + map, + MATERIAL_COLOR_BINDS, + context, + VrmcVrmExpressionsMaterialColorBind.fromMap); + final textureTransformBinds = VrmcVrmExpressionsExpression.getObjectList( map, TEXTURE_TRANSFORM_BINDS, context, @@ -350,7 +350,7 @@ class VrmcVrmExpressionsExpression extends GltfProperty { @override void link(Gltf gltf, Context context) { context.path.add(MORPH_TARGET_BINDS); - for (var i = 0; i < morphTargetBinds.length; i ++) { + for (var i = 0; i < morphTargetBinds.length; i++) { context.path.add(i.toString()); morphTargetBinds[i].link(gltf, context); context.path.removeLast(); @@ -358,7 +358,7 @@ class VrmcVrmExpressionsExpression extends GltfProperty { context.path.removeLast(); context.path.add(MATERIAL_COLOR_BINDS); - for (var i = 0; i < materialColorBinds.length; i ++) { + for (var i = 0; i < materialColorBinds.length; i++) { context.path.add(i.toString()); materialColorBinds[i].link(gltf, context); context.path.removeLast(); @@ -366,7 +366,7 @@ class VrmcVrmExpressionsExpression extends GltfProperty { context.path.removeLast(); context.path.add(TEXTURE_TRANSFORM_BINDS); - for (var i = 0; i < textureTransformBinds.length; i ++) { + for (var i = 0; i < textureTransformBinds.length; i++) { context.path.add(i.toString()); textureTransformBinds[i].link(gltf, context); context.path.removeLast(); From 6919c183f304a496077247e514bd8fca7e7b4917 Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Fri, 19 Aug 2022 21:28:57 +0900 Subject: [PATCH 15/18] feature: support VRMC_springBone Why do "empty node encountered" errors still appear? --- .../ext/VRMC_springBone/vrmc_spring_bone.dart | 142 ++++++++++++++++ .../vrmc_spring_bone_collider.dart | 70 ++++++++ .../vrmc_spring_bone_collider_group.dart | 84 +++++++++ .../vrmc_spring_bone_collider_shape.dart | 160 ++++++++++++++++++ .../vrmc_spring_bone_spring.dart | 150 ++++++++++++++++ .../vrmc_spring_bone_spring_joint.dart | 102 +++++++++++ lib/src/ext/extensions.dart | 2 + 7 files changed, 710 insertions(+) create mode 100644 lib/src/ext/VRMC_springBone/vrmc_spring_bone.dart create mode 100644 lib/src/ext/VRMC_springBone/vrmc_spring_bone_collider.dart create mode 100644 lib/src/ext/VRMC_springBone/vrmc_spring_bone_collider_group.dart create mode 100644 lib/src/ext/VRMC_springBone/vrmc_spring_bone_collider_shape.dart create mode 100644 lib/src/ext/VRMC_springBone/vrmc_spring_bone_spring.dart create mode 100644 lib/src/ext/VRMC_springBone/vrmc_spring_bone_spring_joint.dart diff --git a/lib/src/ext/VRMC_springBone/vrmc_spring_bone.dart b/lib/src/ext/VRMC_springBone/vrmc_spring_bone.dart new file mode 100644 index 0000000..387fc1e --- /dev/null +++ b/lib/src/ext/VRMC_springBone/vrmc_spring_bone.dart @@ -0,0 +1,142 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_springBone/vrmc_spring_bone_collider.dart'; +import 'package:gltf/src/ext/VRMC_springBone/vrmc_spring_bone_collider_group.dart'; +import 'package:gltf/src/ext/VRMC_springBone/vrmc_spring_bone_spring.dart'; +import 'package:gltf/src/ext/extensions.dart'; + +// VRMC_springBone +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_springBone-1.0-beta/schema/VRMC_springBone.schema.json + +const String VRMC_SPRING_BONE = 'VRMC_springBone'; +const String SPEC_VERSION = 'specVersion'; +const String COLLIDERS = 'colliders'; +const String COLLIDER_GROUPS = 'colliderGroups'; +const String SPRINGS = 'springs'; + +const List VRMC_SPRING_BONE_MEMBERS = [ + SPEC_VERSION, + COLLIDERS, + COLLIDER_GROUPS, + SPRINGS, +]; + +const String SPEC_VERSION_10_BETA = '1.0-beta'; + +const List VRMC_VRM_SPEC_VERSIONS = [ + SPEC_VERSION_10_BETA, +]; + +class VrmcSpringBone extends GltfProperty { + final String specVersion; + final List colliders; + final List colliderGroups; + final List springs; + + VrmcSpringBone._(this.specVersion, this.colliders, this.colliderGroups, + this.springs, Map extensions, Object extras) + : super(extensions, extras); + + static SafeList getObjectList(Map map, String name, + Context context, FromMapFunction fromMap) { + if (!map.containsKey(name)) { + return SafeList.empty(name); + } + + final list = getMapList(map, name, context); + final result = SafeList(list.length, name); + + context.path.add(name); + for (var i = 0; i < list.length; i++) { + final item = list[i]; + context.path.add(i.toString()); + result[i] = fromMap(item, context); + context.path.removeLast(); + } + context.path.removeLast(); + + return result; + } + + static VrmcSpringBone fromMap(Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_SPRING_BONE_MEMBERS, context); + } + + final specVersion = getString(map, SPEC_VERSION, context, + list: VRMC_VRM_SPEC_VERSIONS, req: true); + + final colliders = VrmcSpringBone.getObjectList( + map, COLLIDERS, context, VrmcSpringBoneCollider.fromMap); + + final colliderGroups = VrmcSpringBone.getObjectList( + map, COLLIDER_GROUPS, context, VrmcSpringBoneColliderGroup.fromMap); + + final springs = VrmcSpringBone.getObjectList( + map, SPRINGS, context, VrmcSpringBoneSpring.fromMap); + + return VrmcSpringBone._(specVersion, colliders, colliderGroups, springs, + getExtensions(map, VrmcSpringBone, context), getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + context.path.add(COLLIDERS); + + if (colliders != null) { + for (var i = 0; i < colliders.length; i++) { + context.path.add(i.toString()); + colliders[i].link(gltf, context); + context.path.removeLast(); + } + } + + context.path.removeLast(); + + context.path.add(COLLIDER_GROUPS); + + if (colliderGroups != null) { + for (var i = 0; i < colliderGroups.length; i++) { + context.path.add(i.toString()); + colliderGroups[i].link(gltf, context); + context.path.removeLast(); + } + } + + context.path.removeLast(); + + context.path.add(SPRINGS); + + if (springs != null) { + for (var i = 0; i < springs.length; i++) { + context.path.add(i.toString()); + springs[i].link(gltf, context); + context.path.removeLast(); + } + } + + context.path.removeLast(); + } +} + +const Extension vrmcSpringBoneExtension = + Extension(VRMC_SPRING_BONE, { + Gltf: ExtensionDescriptor(VrmcSpringBone.fromMap), +}); diff --git a/lib/src/ext/VRMC_springBone/vrmc_spring_bone_collider.dart b/lib/src/ext/VRMC_springBone/vrmc_spring_bone_collider.dart new file mode 100644 index 0000000..98a0d4c --- /dev/null +++ b/lib/src/ext/VRMC_springBone/vrmc_spring_bone_collider.dart @@ -0,0 +1,70 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_expressions; + +import 'package:gltf/src/base/gltf_property.dart'; +import 'package:gltf/src/ext/VRMC_springBone/vrmc_spring_bone_collider_shape.dart'; + +// collider +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_springBone-1.0-beta/schema/VRMC_springBone.collider.schema.json + +const String NODE = 'node'; +const String SHAPE = 'shape'; + +const List VRMC_SPRING_BONE_COLLIDER_MEMBERS = [ + NODE, + SHAPE, +]; + +class VrmcSpringBoneCollider extends GltfProperty { + final int nodeIndex; + final VrmcSpringBoneColliderShape shape; + + Node node; + + VrmcSpringBoneCollider._( + this.nodeIndex, this.shape, Map extensions, Object extras) + : super(extensions, extras); + + static VrmcSpringBoneCollider fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_SPRING_BONE_COLLIDER_MEMBERS, context); + } + + return VrmcSpringBoneCollider._( + getIndex(map, NODE, context), + getObjectFromInnerMap( + map, SHAPE, context, VrmcSpringBoneColliderShape.fromMap), + getExtensions(map, VrmcSpringBoneCollider, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + node = gltf.nodes[nodeIndex]; + + if (context.validate && nodeIndex != -1) { + if (node == null) { + context.addIssue(LinkError.unresolvedReference, + name: NODE, args: [nodeIndex]); + } else { + node.markAsUsed(); + } + } + } +} diff --git a/lib/src/ext/VRMC_springBone/vrmc_spring_bone_collider_group.dart b/lib/src/ext/VRMC_springBone/vrmc_spring_bone_collider_group.dart new file mode 100644 index 0000000..3837268 --- /dev/null +++ b/lib/src/ext/VRMC_springBone/vrmc_spring_bone_collider_group.dart @@ -0,0 +1,84 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_expressions; + +import 'package:gltf/src/base/gltf_property.dart'; +import 'package:gltf/src/ext/VRMC_springBone/vrmc_spring_bone.dart'; +import 'package:gltf/src/ext/VRMC_springBone/vrmc_spring_bone_collider.dart'; + +// collider group +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_springBone-1.0-beta/schema/VRMC_springBone.colliderGroup.schema.json + +const String NAME = 'name'; +const String COLLIDERS = 'colliders'; + +const List VRMC_SPRING_BONE_COLLIDER_MEMBERS = [ + NAME, + COLLIDERS, +]; + +class VrmcSpringBoneColliderGroup extends GltfProperty { + final String name; + final List colliderIndices; + + List colliders; + + VrmcSpringBoneColliderGroup._(this.name, this.colliderIndices, + Map extensions, Object extras) + : super(extensions, extras); + + static VrmcSpringBoneColliderGroup fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_SPRING_BONE_COLLIDER_MEMBERS, context); + } + + return VrmcSpringBoneColliderGroup._( + getString(map, NAME, context), + getIndicesList(map, COLLIDERS, context, req: true), + getExtensions(map, VrmcSpringBoneColliderGroup, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + context.path.add(COLLIDERS); + + if (colliderIndices != null) { + colliders = List.filled(colliderIndices.length, null); + + final ext = gltf.extensions[VRMC_SPRING_BONE]; + if (ext is VrmcSpringBone) { + for (var i = 0; i < colliderIndices.length; i++) { + context.path.add(i.toString()); + + final index = colliderIndices[i]; + final collider = ext.colliders[index]; + colliders[i] = collider; + + if (context.validate && collider == null) { + context.addIssue(LinkError.unresolvedReference, args: [index]); + } + + context.path.removeLast(); + } + } + } + + context.path.removeLast(); + } +} diff --git a/lib/src/ext/VRMC_springBone/vrmc_spring_bone_collider_shape.dart b/lib/src/ext/VRMC_springBone/vrmc_spring_bone_collider_shape.dart new file mode 100644 index 0000000..8656a50 --- /dev/null +++ b/lib/src/ext/VRMC_springBone/vrmc_spring_bone_collider_shape.dart @@ -0,0 +1,160 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_expressions; + +import 'package:gltf/src/base/gltf_property.dart'; +import 'package:vector_math/vector_math.dart'; + +// sphere + +const String OFFSET = 'offset'; +const String RADIUS = 'radius'; + +const List VRMC_SPRING_BONE_COLLIDER_SHAPE_SPHERE_MEMBERS = [ + OFFSET, + RADIUS, +]; + +class VrmcSpringBoneColliderShapeSphere extends GltfProperty { + final Vector3 offset; + final double radius; + + VrmcSpringBoneColliderShapeSphere._( + this.offset, this.radius, Map extensions, Object extras) + : super(extensions, extras); + + static VrmcSpringBoneColliderShapeSphere fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers( + map, VRMC_SPRING_BONE_COLLIDER_SHAPE_SPHERE_MEMBERS, context); + } + + Vector3 offset; + if (map.containsKey(OFFSET)) { + final list = getFloatList(map, OFFSET, context, lengthsList: const [3]); + if (list != null) { + offset = Vector3.array(list); + } + } + + return VrmcSpringBoneColliderShapeSphere._( + offset, + getFloat(map, RADIUS, context), + getExtensions(map, VrmcSpringBoneColliderShapeSphere, context), + getExtras(map, context)); + } +} + +// capsule + +const String TAIL = 'tail'; + +const List VRMC_SPRING_BONE_COLLIDER_SHAPE_CAPSULE_MEMBERS = [ + OFFSET, + RADIUS, + TAIL, +]; + +class VrmcSpringBoneColliderShapeCapsule extends GltfProperty { + final Vector3 offset; + final double radius; + final Vector3 tail; + + VrmcSpringBoneColliderShapeCapsule._(this.offset, this.radius, this.tail, + Map extensions, Object extras) + : super(extensions, extras); + + static VrmcSpringBoneColliderShapeCapsule fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers( + map, VRMC_SPRING_BONE_COLLIDER_SHAPE_CAPSULE_MEMBERS, context); + } + + Vector3 offset; + if (map.containsKey(OFFSET)) { + final list = getFloatList(map, OFFSET, context, lengthsList: const [3]); + if (list != null) { + offset = Vector3.array(list); + } + } + + Vector3 tail; + if (map.containsKey(TAIL)) { + final list = getFloatList(map, TAIL, context, lengthsList: const [3]); + if (list != null) { + tail = Vector3.array(list); + } + } + + return VrmcSpringBoneColliderShapeCapsule._( + offset, + getFloat(map, RADIUS, context), + tail, + getExtensions(map, VrmcSpringBoneColliderShapeCapsule, context), + getExtras(map, context)); + } +} + +// shape +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_springBone-1.0-beta/schema/VRMC_springBone.shape.schema.json + +const String SPHERE = 'sphere'; +const String CAPSULE = 'capsule'; + +const List VRMC_SPRING_BONE_COLLIDER_SHAPE_MEMBERS = [ + SPHERE, + CAPSULE, +]; + +class VrmcSpringBoneColliderShape extends GltfProperty { + final VrmcSpringBoneColliderShapeSphere sphere; + final VrmcSpringBoneColliderShapeCapsule capsule; + + Node node; + + VrmcSpringBoneColliderShape._( + this.sphere, this.capsule, Map extensions, Object extras) + : super(extensions, extras); + + static VrmcSpringBoneColliderShape fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_SPRING_BONE_COLLIDER_SHAPE_MEMBERS, context); + } + + final sphere = getObjectFromInnerMap( + map, SPHERE, context, VrmcSpringBoneColliderShapeSphere.fromMap); + final capsule = getObjectFromInnerMap( + map, CAPSULE, context, VrmcSpringBoneColliderShapeCapsule.fromMap); + + var oneOfCount = 0; + oneOfCount += sphere != null ? 1 : 0; + oneOfCount += capsule != null ? 1 : 0; + + if (oneOfCount != 1) { + context.addIssue(SchemaError.oneOfMismatch, args: [SPHERE, CAPSULE]); + } + + return VrmcSpringBoneColliderShape._( + sphere, + capsule, + getExtensions(map, VrmcSpringBoneColliderShape, context), + getExtras(map, context)); + } +} diff --git a/lib/src/ext/VRMC_springBone/vrmc_spring_bone_spring.dart b/lib/src/ext/VRMC_springBone/vrmc_spring_bone_spring.dart new file mode 100644 index 0000000..e19064e --- /dev/null +++ b/lib/src/ext/VRMC_springBone/vrmc_spring_bone_spring.dart @@ -0,0 +1,150 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_expressions; + +import 'package:gltf/src/base/gltf_property.dart'; +import 'package:gltf/src/ext/VRMC_springBone/vrmc_spring_bone.dart'; +import 'package:gltf/src/ext/VRMC_springBone/vrmc_spring_bone_collider_group.dart'; +import 'package:gltf/src/ext/VRMC_springBone/vrmc_spring_bone_spring_joint.dart'; + +// spring +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_springBone-1.0-beta/schema/VRMC_springBone.spring.schema.json + +const String NAME = 'name'; +const String JOINTS = 'joints'; +const String COLLIDER_GROUPS = 'colliderGroups'; +const String CENTER = 'center'; + +const List VRMC_SPRING_BONE_SPRING_MEMBERS = [ + NAME, + JOINTS, + COLLIDER_GROUPS, + CENTER, +]; + +class VrmcSpringBoneSpring extends GltfProperty { + final String name; + final List joints; + final List colliderGroupIndices; + final int centerIndex; + + List colliderGroups; + Node center; + + VrmcSpringBoneSpring._(this.name, this.joints, this.colliderGroupIndices, + this.centerIndex, Map extensions, Object extras) + : super(extensions, extras); + + static SafeList getObjectList(Map map, String name, + Context context, FromMapFunction fromMap, + {bool req = false}) { + if (!map.containsKey(name)) { + if (context.validate && req) { + context.addIssue(SchemaError.undefinedProperty, args: [name]); + } + + return SafeList.empty(name); + } + + final list = getMapList(map, name, context); + final result = SafeList(list.length, name); + + context.path.add(name); + for (var i = 0; i < list.length; i++) { + final item = list[i]; + context.path.add(i.toString()); + result[i] = fromMap(item, context); + context.path.removeLast(); + } + context.path.removeLast(); + + return result; + } + + static VrmcSpringBoneSpring fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_SPRING_BONE_SPRING_MEMBERS, context); + } + + final joints = VrmcSpringBoneSpring.getObjectList( + map, JOINTS, context, VrmcSpringBoneSpringJoint.fromMap, + req: true); + + return VrmcSpringBoneSpring._( + getString(map, NAME, context), + joints, + getIndicesList(map, COLLIDER_GROUPS, context, req: false), + getIndex(map, CENTER, context, req: false), + getExtensions(map, VrmcSpringBoneSpring, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + // joints + context.path.add(JOINTS); + + if (joints != null) { + for (var i = 0; i < joints.length; i++) { + context.path.add(i.toString()); + joints[i].link(gltf, context); + context.path.removeLast(); + } + } + + context.path.removeLast(); + + // collider groups + context.path.add(COLLIDER_GROUPS); + + if (colliderGroupIndices != null) { + colliderGroups = List.filled(colliderGroupIndices.length, null); + + final ext = gltf.extensions[VRMC_SPRING_BONE]; + if (ext is VrmcSpringBone) { + for (var i = 0; i < colliderGroupIndices.length; i++) { + context.path.add(i.toString()); + + final index = colliderGroupIndices[i]; + final colliderGroup = ext.colliderGroups[index]; + colliderGroups[i] = colliderGroup; + + if (context.validate && colliderGroup == null) { + context.addIssue(LinkError.unresolvedReference, args: [index]); + } + + context.path.removeLast(); + } + } + } + + context.path.removeLast(); + + // center + center = gltf.nodes[centerIndex]; + + if (context.validate && centerIndex != -1) { + if (center == null) { + context.addIssue(LinkError.unresolvedReference, + name: CENTER, args: [centerIndex]); + } else { + center.markAsUsed(); + } + } + } +} diff --git a/lib/src/ext/VRMC_springBone/vrmc_spring_bone_spring_joint.dart b/lib/src/ext/VRMC_springBone/vrmc_spring_bone_spring_joint.dart new file mode 100644 index 0000000..fc2e680 --- /dev/null +++ b/lib/src/ext/VRMC_springBone/vrmc_spring_bone_spring_joint.dart @@ -0,0 +1,102 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_expressions; + +import 'package:gltf/src/base/gltf_property.dart'; +import 'package:vector_math/vector_math.dart'; + +// spring +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_springBone-1.0-beta/schema/VRMC_springBone.joint.schema.json + +const String NODE = 'node'; +const String HIT_RADIUS = 'hitRadius'; +const String STIFFNESS = 'stiffness'; +const String GRAVITY_POWER = 'gravityPower'; +const String GRAVITY_DIR = 'gravityDir'; +const String DRAG_FORCE = 'dragForce'; + +const List VRMC_SPRING_BONE_SPRING_JOINT_MEMBERS = [ + NODE, + HIT_RADIUS, + STIFFNESS, + GRAVITY_POWER, + GRAVITY_DIR, + DRAG_FORCE, +]; + +class VrmcSpringBoneSpringJoint extends GltfProperty { + final int nodeIndex; + final double hitRadius; + final double stiffness; + final double gravityPower; + final Vector3 gravityDir; + final double dragForce; + + Node node; + + VrmcSpringBoneSpringJoint._( + this.nodeIndex, + this.hitRadius, + this.stiffness, + this.gravityPower, + this.gravityDir, + this.dragForce, + Map extensions, + Object extras) + : super(extensions, extras); + + static VrmcSpringBoneSpringJoint fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_SPRING_BONE_SPRING_JOINT_MEMBERS, context); + } + + Vector3 gravityDir; + if (map.containsKey(GRAVITY_DIR)) { + final list = + getFloatList(map, GRAVITY_DIR, context, lengthsList: const [3]); + if (list != null) { + gravityDir = Vector3.array(list); + } + } + + return VrmcSpringBoneSpringJoint._( + getIndex(map, NODE, context, req: true), + getFloat(map, HIT_RADIUS, context), + getFloat(map, STIFFNESS, context), + getFloat(map, GRAVITY_POWER, context), + gravityDir, + getFloat(map, DRAG_FORCE, context), + getExtensions(map, VrmcSpringBoneSpringJoint, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + // node + node = gltf.nodes[nodeIndex]; + + if (context.validate && nodeIndex != -1) { + if (node == null) { + context.addIssue(LinkError.unresolvedReference, + name: NODE, args: [nodeIndex]); + } else { + node.markAsUsed(); + } + } + } +} diff --git a/lib/src/ext/extensions.dart b/lib/src/ext/extensions.dart index f8f59f8..b8263da 100644 --- a/lib/src/ext/extensions.dart +++ b/lib/src/ext/extensions.dart @@ -32,6 +32,7 @@ import 'package:gltf/src/ext/KHR_materials_variants/KHR_materials_variants.dart' 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_springBone/vrmc_spring_bone.dart'; import 'package:gltf/src/ext/VRMC_vrm/vrmc_vrm.dart'; import 'package:gltf/src/hash.dart'; import 'package:meta/meta.dart'; @@ -113,6 +114,7 @@ const List kDefaultExtensions = [ khrMaterialsVolumeExtension, khrMeshQuantizationExtension, khrTextureTransformExtension, + vrmcSpringBoneExtension, vrmcVrmExtension, ]; From 9ee42403e1bfd4f8755248425bb00092ba35f3f4 Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Thu, 25 Aug 2022 20:01:33 +0900 Subject: [PATCH 16/18] feature: support VRMC_node_constraint --- .../vrmc_node_constraint.dart | 77 ++++++++++++++++ .../vrmc_node_constraint_aim_constraint.dart | 89 +++++++++++++++++++ .../vrmc_node_constraint_constraint.dart | 88 ++++++++++++++++++ .../vrmc_node_constraint_roll_constraint.dart | 83 +++++++++++++++++ ...c_node_constraint_rotation_constraint.dart | 69 ++++++++++++++ lib/src/ext/extensions.dart | 2 + 6 files changed, 408 insertions(+) create mode 100644 lib/src/ext/VRMC_node_constraint/vrmc_node_constraint.dart create mode 100644 lib/src/ext/VRMC_node_constraint/vrmc_node_constraint_aim_constraint.dart create mode 100644 lib/src/ext/VRMC_node_constraint/vrmc_node_constraint_constraint.dart create mode 100644 lib/src/ext/VRMC_node_constraint/vrmc_node_constraint_roll_constraint.dart create mode 100644 lib/src/ext/VRMC_node_constraint/vrmc_node_constraint_rotation_constraint.dart 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..663da61 --- /dev/null +++ b/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint.dart @@ -0,0 +1,77 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_node_constraint_constraint.dart'; +import 'package:gltf/src/ext/extensions.dart'; + +// VRMC_node_constraint +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_node_constraint-1.0_beta/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, +]; + +const String SPEC_VERSION_10_BETA = '1.0-beta'; + +const List VRMC_VRM_SPEC_VERSIONS = [ + SPEC_VERSION_10_BETA, +]; + +class VrmcNodeConstraint extends GltfProperty { + final String specVersion; + final VrmcNodeConstraintConstraint 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, + list: VRMC_VRM_SPEC_VERSIONS, req: true); + + return VrmcNodeConstraint._( + specVersion, + getObjectFromInnerMap( + map, CONSTRAINT, context, VrmcNodeConstraintConstraint.fromMap, + req: true), + getExtensions(map, VrmcNodeConstraint, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + context.path.add(CONSTRAINT); + constraint?.link(gltf, context); + context.path.removeLast(); + } +} + +const Extension vrmcNodeConstraintExtension = + Extension(VRMC_NODE_CONSTRAINT, { + Node: ExtensionDescriptor(VrmcNodeConstraint.fromMap), +}); diff --git a/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint_aim_constraint.dart b/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint_aim_constraint.dart new file mode 100644 index 0000000..a247831 --- /dev/null +++ b/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint_aim_constraint.dart @@ -0,0 +1,89 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_expressions; + +import 'package:gltf/src/base/gltf_property.dart'; + +// aim constraint +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_node_constraint-1.0_beta/schema/VRMC_node_constraint.aimConstraint.schema.json + +const String SOURCE = 'source'; +const String AIM_AXIS = 'aimAxis'; +const String WEIGHT = 'weight'; + +const List VRMC_NODE_CONSTRAINT_AIM_CONSTRAINT_MEMBERS = [ + SOURCE, + AIM_AXIS, + WEIGHT, +]; + +const String POSITIVE_X = 'PositiveX'; +const String NEGATIVE_X = 'NegativeX'; +const String POSITIVE_Y = 'PositiveY'; +const String NEGATIVE_Y = 'NegativeY'; +const String POSITIVE_Z = 'PositiveZ'; +const String NEGATIVE_Z = 'NegativeZ'; + +const List VRMC_NODE_CONSTRAINT_AIM_CONSTRAINT_AIM_AXES = [ + POSITIVE_X, + NEGATIVE_X, + POSITIVE_Y, + NEGATIVE_Y, + POSITIVE_Z, + NEGATIVE_Z, +]; + +class VrmcNodeConstraintAimConstraint extends GltfProperty { + final int sourceIndex; + final String aimAxis; + final double weight; + + Node source; + + VrmcNodeConstraintAimConstraint._(this.sourceIndex, this.aimAxis, + this.weight, Map extensions, Object extras) + : super(extensions, extras); + + static VrmcNodeConstraintAimConstraint fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_NODE_CONSTRAINT_AIM_CONSTRAINT_MEMBERS, context); + } + + return VrmcNodeConstraintAimConstraint._( + getIndex(map, SOURCE, context, req: true), + getString(map, AIM_AXIS, context, + list: VRMC_NODE_CONSTRAINT_AIM_CONSTRAINT_AIM_AXES, req: true), + getFloat(map, WEIGHT, context, min: 0, max: 1, def: 1), + getExtensions(map, VrmcNodeConstraintAimConstraint, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + source = gltf.nodes[sourceIndex]; + + if (context.validate && sourceIndex != -1) { + if (source == null) { + context.addIssue(LinkError.unresolvedReference, + name: SOURCE, args: [sourceIndex]); + } else { + source.markAsUsed(); + } + } + } +} diff --git a/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint_constraint.dart b/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint_constraint.dart new file mode 100644 index 0000000..8fc00a9 --- /dev/null +++ b/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint_constraint.dart @@ -0,0 +1,88 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_expressions; + +import 'package:gltf/src/base/gltf_property.dart'; +import 'package:gltf/src/ext/VRMC_node_constraint/vrmc_node_constraint_aim_constraint.dart'; +import 'package:gltf/src/ext/VRMC_node_constraint/vrmc_node_constraint_roll_constraint.dart'; +import 'package:gltf/src/ext/VRMC_node_constraint/vrmc_node_constraint_rotation_constraint.dart'; + +// constraint +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_node_constraint-1.0_beta/schema/VRMC_node_constraint.constraint.schema.json + +const String ROLL = 'roll'; +const String AIM = 'aim'; +const String ROTATION = 'rotation'; + +const List VRMC_NODE_CONSTRAINT_CONSTRAINT_MEMBERS = [ + ROLL, + AIM, + ROTATION, +]; + +class VrmcNodeConstraintConstraint extends GltfProperty { + final VrmcNodeConstraintRollConstraint roll; + final VrmcNodeConstraintAimConstraint aim; + final VrmcNodeConstraintRotationConstraint rotation; + + VrmcNodeConstraintConstraint._( + this.roll, this.aim, this.rotation, Map extensions, Object extras) + : super(extensions, extras); + + static VrmcNodeConstraintConstraint fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_NODE_CONSTRAINT_CONSTRAINT_MEMBERS, context); + } + + final roll = getObjectFromInnerMap( + map, ROLL, context, VrmcNodeConstraintRollConstraint.fromMap); + final aim = getObjectFromInnerMap( + map, AIM, context, VrmcNodeConstraintAimConstraint.fromMap); + final rotation = getObjectFromInnerMap( + map, ROTATION, context, VrmcNodeConstraintRotationConstraint.fromMap); + + var oneOfCount = 0; + oneOfCount += roll != null ? 1 : 0; + oneOfCount += aim != null ? 1 : 0; + oneOfCount += rotation != null ? 1 : 0; + + if (oneOfCount != 1) { + context.addIssue(SchemaError.oneOfMismatch, args: [ROLL, AIM, ROTATION]); + } + + return VrmcNodeConstraintConstraint._( + roll, aim, rotation, + getExtensions(map, VrmcNodeConstraintConstraint, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + context.path.add(ROLL); + roll?.link(gltf, context); + context.path.removeLast(); + + context.path.add(AIM); + aim?.link(gltf, context); + context.path.removeLast(); + + context.path.add(ROTATION); + rotation?.link(gltf, context); + context.path.removeLast(); + } +} diff --git a/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint_roll_constraint.dart b/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint_roll_constraint.dart new file mode 100644 index 0000000..eb8c038 --- /dev/null +++ b/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint_roll_constraint.dart @@ -0,0 +1,83 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_expressions; + +import 'package:gltf/src/base/gltf_property.dart'; + +// roll constraint +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_node_constraint-1.0_beta/schema/VRMC_node_constraint.rollConstraint.schema.json + +const String SOURCE = 'source'; +const String ROLL_AXIS = 'rollAxis'; +const String WEIGHT = 'weight'; + +const List VRMC_NODE_CONSTRAINT_ROLL_CONSTRAINT_MEMBERS = [ + SOURCE, + ROLL_AXIS, + WEIGHT, +]; + +const String X = 'X'; +const String Y = 'Y'; +const String Z = 'Z'; + +const List VRMC_NODE_CONSTRAINT_ROLL_CONSTRAINT_ROLL_AXES = [ + X, + Y, + Z, +]; + +class VrmcNodeConstraintRollConstraint extends GltfProperty { + final int sourceIndex; + final String rollAxis; + final double weight; + + Node source; + + VrmcNodeConstraintRollConstraint._(this.sourceIndex, this.rollAxis, + this.weight, Map extensions, Object extras) + : super(extensions, extras); + + static VrmcNodeConstraintRollConstraint fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_NODE_CONSTRAINT_ROLL_CONSTRAINT_MEMBERS, context); + } + + return VrmcNodeConstraintRollConstraint._( + getIndex(map, SOURCE, context, req: true), + getString(map, ROLL_AXIS, context, + list: VRMC_NODE_CONSTRAINT_ROLL_CONSTRAINT_ROLL_AXES, req: true), + getFloat(map, WEIGHT, context, min: 0, max: 1, def: 1), + getExtensions(map, VrmcNodeConstraintRollConstraint, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + source = gltf.nodes[sourceIndex]; + + if (context.validate && sourceIndex != -1) { + if (source == null) { + context.addIssue(LinkError.unresolvedReference, + name: SOURCE, args: [sourceIndex]); + } else { + source.markAsUsed(); + } + } + } +} diff --git a/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint_rotation_constraint.dart b/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint_rotation_constraint.dart new file mode 100644 index 0000000..7465246 --- /dev/null +++ b/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint_rotation_constraint.dart @@ -0,0 +1,69 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_expressions; + +import 'package:gltf/src/base/gltf_property.dart'; + +// rotation constraint +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_node_constraint-1.0_beta/schema/VRMC_node_constraint.rotationConstraint.schema.json + +const String SOURCE = 'source'; +const String WEIGHT = 'weight'; + +const List VRMC_NODE_CONSTRAINT_ROTATION_CONSTRAINT_MEMBERS = [ + SOURCE, + WEIGHT, +]; + +class VrmcNodeConstraintRotationConstraint extends GltfProperty { + final int sourceIndex; + final double weight; + + Node source; + + VrmcNodeConstraintRotationConstraint._(this.sourceIndex, this.weight, + Map extensions, Object extras) + : super(extensions, extras); + + static VrmcNodeConstraintRotationConstraint fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers( + map, VRMC_NODE_CONSTRAINT_ROTATION_CONSTRAINT_MEMBERS, context); + } + + return VrmcNodeConstraintRotationConstraint._( + getIndex(map, SOURCE, context, req: true), + getFloat(map, WEIGHT, context, min: 0, max: 1, def: 1), + getExtensions(map, VrmcNodeConstraintRotationConstraint, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + source = gltf.nodes[sourceIndex]; + + if (context.validate && sourceIndex != -1) { + if (source == null) { + context.addIssue(LinkError.unresolvedReference, + name: SOURCE, args: [sourceIndex]); + } else { + source.markAsUsed(); + } + } + } +} diff --git a/lib/src/ext/extensions.dart b/lib/src/ext/extensions.dart index b8263da..43f5ea2 100644 --- a/lib/src/ext/extensions.dart +++ b/lib/src/ext/extensions.dart @@ -32,6 +32,7 @@ import 'package:gltf/src/ext/KHR_materials_variants/KHR_materials_variants.dart' 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_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'; @@ -114,6 +115,7 @@ const List kDefaultExtensions = [ khrMaterialsVolumeExtension, khrMeshQuantizationExtension, khrTextureTransformExtension, + vrmcNodeConstraintExtension, vrmcSpringBoneExtension, vrmcVrmExtension, ]; From d1cc529683900f3a111194cbb75286d1e7c03ed9 Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Thu, 25 Aug 2022 20:06:30 +0900 Subject: [PATCH 17/18] refactor: slight refactor --- lib/src/ext/VRMC_node_constraint/vrmc_node_constraint.dart | 4 ++-- lib/src/ext/VRMC_springBone/vrmc_spring_bone.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint.dart b/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint.dart index 663da61..aec099e 100644 --- a/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint.dart +++ b/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint.dart @@ -34,7 +34,7 @@ const List VRMC_NODE_CONSTRAINT_MEMBERS = [ const String SPEC_VERSION_10_BETA = '1.0-beta'; -const List VRMC_VRM_SPEC_VERSIONS = [ +const List VRMC_NODE_CONSTRAINT_SPEC_VERSIONS = [ SPEC_VERSION_10_BETA, ]; @@ -52,7 +52,7 @@ class VrmcNodeConstraint extends GltfProperty { } final specVersion = getString(map, SPEC_VERSION, context, - list: VRMC_VRM_SPEC_VERSIONS, req: true); + list: VRMC_NODE_CONSTRAINT_SPEC_VERSIONS, req: true); return VrmcNodeConstraint._( specVersion, diff --git a/lib/src/ext/VRMC_springBone/vrmc_spring_bone.dart b/lib/src/ext/VRMC_springBone/vrmc_spring_bone.dart index 387fc1e..5b9a77a 100644 --- a/lib/src/ext/VRMC_springBone/vrmc_spring_bone.dart +++ b/lib/src/ext/VRMC_springBone/vrmc_spring_bone.dart @@ -40,7 +40,7 @@ const List VRMC_SPRING_BONE_MEMBERS = [ const String SPEC_VERSION_10_BETA = '1.0-beta'; -const List VRMC_VRM_SPEC_VERSIONS = [ +const List VRMC_SPRING_BONE_SPEC_VERSIONS = [ SPEC_VERSION_10_BETA, ]; @@ -81,7 +81,7 @@ class VrmcSpringBone extends GltfProperty { } final specVersion = getString(map, SPEC_VERSION, context, - list: VRMC_VRM_SPEC_VERSIONS, req: true); + list: VRMC_SPRING_BONE_SPEC_VERSIONS, req: true); final colliders = VrmcSpringBone.getObjectList( map, COLLIDERS, context, VrmcSpringBoneCollider.fromMap); From 56fad843a8dad616e98b0a76da7e5f8dd0a66fc3 Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Thu, 25 Aug 2022 21:32:07 +0900 Subject: [PATCH 18/18] feature: support VRMC_materials_mtoon --- lib/src/errors.dart | 7 + .../khr_texture_transform.dart | 4 + .../vrmc_materials_mtoon.dart | 280 ++++++++++++++++++ ...ials_mtoon_shading_shift_texture_info.dart | 92 ++++++ lib/src/ext/extensions.dart | 2 + lib/src/utils.dart | 27 ++ 6 files changed, 412 insertions(+) create mode 100644 lib/src/ext/VRMC_materials_mtoon/vrmc_materials_mtoon.dart create mode 100644 lib/src/ext/VRMC_materials_mtoon/vrmc_materials_mtoon_shading_shift_texture_info.dart diff --git a/lib/src/errors.dart b/lib/src/errors.dart index 68c8df7..f547fb6 100644 --- a/lib/src/errors.dart +++ b/lib/src/errors.dart @@ -827,6 +827,13 @@ class LinkError extends IssueType { '${args[1]} available.', Severity.Information); + static final LinkError vrmcMaterialsMtoonInvalidRenderQueueOffset = + LinkError._( + 'VRMC_MATERIALS_MTOON_INVALID_RENDER_QUEUE_OFFSET', + (args) => 'renderQueueOffsetNumber ${args[0]} is not compatible ' + 'with ${args[1]} specified in this material: ${args[2]}.'); + + LinkError._(String type, ErrorFunction message, [Severity severity = Severity.Error]) : super(type, message, severity); diff --git a/lib/src/ext/KHR_texture_transform/khr_texture_transform.dart b/lib/src/ext/KHR_texture_transform/khr_texture_transform.dart index 9129fa9..d2c2f44 100644 --- a/lib/src/ext/KHR_texture_transform/khr_texture_transform.dart +++ b/lib/src/ext/KHR_texture_transform/khr_texture_transform.dart @@ -17,11 +17,13 @@ library gltf.extensions.khr_texture_transform; import 'package:gltf/src/base/gltf_property.dart'; +import 'package:gltf/src/ext/VRMC_materials_mtoon/vrmc_materials_mtoon_shading_shift_texture_info.dart'; import 'package:gltf/src/ext/extensions.dart'; // EXT_texture_transform const String KHR_TEXTURE_TRANSFORM = 'KHR_texture_transform'; const String OFFSET = 'offset'; +const String SCALE = 'scale'; const List KHR_TEXTURE_TRANSFORM_MEMBERS = [ OFFSET, @@ -74,4 +76,6 @@ const Extension khrTextureTransformExtension = TextureInfo: ExtensionDescriptor(KhrTextureTransform.fromMap), NormalTextureInfo: ExtensionDescriptor(KhrTextureTransform.fromMap), OcclusionTextureInfo: ExtensionDescriptor(KhrTextureTransform.fromMap), + VrmcMaterialsMtoonShadingShiftTextureInfo: + ExtensionDescriptor(KhrTextureTransform.fromMap), }); diff --git a/lib/src/ext/VRMC_materials_mtoon/vrmc_materials_mtoon.dart b/lib/src/ext/VRMC_materials_mtoon/vrmc_materials_mtoon.dart new file mode 100644 index 0000000..cf5478b --- /dev/null +++ b/lib/src/ext/VRMC_materials_mtoon/vrmc_materials_mtoon.dart @@ -0,0 +1,280 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_materials_mtoon; + +import 'package:gltf/src/base/gltf_property.dart'; +import 'package:gltf/src/ext/VRMC_materials_mtoon/vrmc_materials_mtoon_shading_shift_texture_info.dart'; +import 'package:gltf/src/ext/extensions.dart'; + +// VRMC_materials_mtoon +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_materials_mtoon-1.0-beta/schema/VRMC_materials_mtoon.schema.json + +const String VRMC_MATERIALS_MTOON = 'VRMC_materials_mtoon'; +const String SPEC_VERSION = 'specVersion'; +const String TRANSPARENT_WITH_Z_WRITE = 'transparentWithZWrite'; +const String RENDER_QUEUE_OFFSET_NUMBER = 'renderQueueOffsetNumber'; +const String SHADE_COLOR_FACTOR = 'shadeColorFactor'; +const String SHADE_MULTIPLY_TEXTURE = 'shadeMultiplyTexture'; +const String SHADING_SHIFT_FACTOR = 'shadingShiftFactor'; +const String SHADING_SHIFT_TEXTURE = 'shadingShiftTexture'; +const String SHADING_TOONY_FACTOR = 'shadingToonyFactor'; +const String GI_EQUALIZATION_FACTOR = 'giEqualizationFactor'; +const String MATCAP_FACTOR = 'matcapFactor'; +const String MATCAP_TEXTURE = 'matcapTexture'; +const String PARAMETRIC_RIM_COLOR_FACTOR = 'parametricRimColorFactor'; +const String RIM_MULTIPLY_TEXTURE = 'rimMultiplyTexture'; +const String RIM_LIGHTING_MIX_FACTOR = 'rimLightingMixFactor'; +const String PARAMETRIC_RIM_FRESNEL_POWER_FACTOR = + 'parametricRimFresnelPowerFactor'; +const String PARAMETRIC_RIM_LIFT_FACTOR = 'parametricRimLiftFactor'; +const String OUTLINE_WIDTH_MODE = 'outlineWidthMode'; +const String OUTLINE_WIDTH_FACTOR = 'outlineWidthFactor'; +const String OUTLINE_WIDTH_MULTIPLY_TEXTURE = 'outlineWidthMultiplyTexture'; +const String OUTLINE_COLOR_FACTOR = 'outlineColorFactor'; +const String OUTLINE_LIGHTING_MIX_FACTOR = 'outlineLightingMixFactor'; +const String UV_ANIMATION_MASK_TEXTURE = 'uvAnimationMaskTexture'; +const String UV_ANIMATION_SCROLL_X_SPEED_FACTOR = + 'uvAnimationScrollXSpeedFactor'; +const String UV_ANIMATION_SCROLL_Y_SPEED_FACTOR = + 'uvAnimationScrollYSpeedFactor'; +const String UV_ANIMATION_ROTATION_SPEED_FACTOR = + 'uvAnimationRotationSpeedFactor'; + +const List VRMC_NODE_CONSTRAINT_MEMBERS = [ + SPEC_VERSION, + TRANSPARENT_WITH_Z_WRITE, + RENDER_QUEUE_OFFSET_NUMBER, + SHADE_COLOR_FACTOR, + SHADE_MULTIPLY_TEXTURE, + SHADING_SHIFT_FACTOR, + SHADING_SHIFT_TEXTURE, + SHADING_TOONY_FACTOR, + GI_EQUALIZATION_FACTOR, + MATCAP_FACTOR, + MATCAP_TEXTURE, + PARAMETRIC_RIM_COLOR_FACTOR, + RIM_MULTIPLY_TEXTURE, + RIM_LIGHTING_MIX_FACTOR, + PARAMETRIC_RIM_FRESNEL_POWER_FACTOR, + PARAMETRIC_RIM_LIFT_FACTOR, + OUTLINE_WIDTH_MODE, + OUTLINE_WIDTH_FACTOR, + OUTLINE_WIDTH_MULTIPLY_TEXTURE, + OUTLINE_COLOR_FACTOR, + OUTLINE_LIGHTING_MIX_FACTOR, + UV_ANIMATION_MASK_TEXTURE, + UV_ANIMATION_SCROLL_X_SPEED_FACTOR, + UV_ANIMATION_SCROLL_Y_SPEED_FACTOR, + UV_ANIMATION_ROTATION_SPEED_FACTOR, +]; + +const String SPEC_VERSION_10_BETA = '1.0-beta'; + +const List VRMC_MATERIALS_MTOON_SPEC_VERSIONS = [ + SPEC_VERSION_10_BETA, +]; + +const String NONE = 'none'; +const String WORLD_COORDINATES = 'worldCoordinates'; +const String SCREEN_COORDINATES = 'screenCoordinates'; + +const List VRMC_MATERIALS_MTOON_OUTLINE_WIDTH_MODES = [ + NONE, + WORLD_COORDINATES, + SCREEN_COORDINATES, +]; + +class VrmcMaterialsMtoon extends GltfProperty { + final String specVersion; + final bool transparentWithZWrite; + final int renderQueueOffsetNumber; + final List shadeColorFactor; + final TextureInfo shadeMultiplyTexture; + final double shadingShiftFactor; + final VrmcMaterialsMtoonShadingShiftTextureInfo shadingShiftTexture; + final double shadingToonyFactor; + final double giEqualizationFactor; + final List matcapFactor; + final TextureInfo matcapTexture; + final List parametricRimColorFactor; + final TextureInfo rimMultiplyTexture; + final double rimLightingMixFactor; + final double parametricRimFresnelPowerFactor; + final double parametricRimLiftFactor; + final String outlineWidthMode; + final double outlineWidthFactor; + final TextureInfo outlineWidthMultiplyTexture; + final List outlineColorFactor; + final double outlineLightingMixFactor; + final TextureInfo uvAnimationMaskTexture; + final double uvAnimationScrollXSpeedFactor; + final double uvAnimationScrollYSpeedFactor; + final double uvAnimationRotationSpeedFactor; + + VrmcMaterialsMtoon._( + this.specVersion, + this.transparentWithZWrite, + this.renderQueueOffsetNumber, + this.shadeColorFactor, + this.shadeMultiplyTexture, + this.shadingShiftFactor, + this.shadingShiftTexture, + this.shadingToonyFactor, + this.giEqualizationFactor, + this.matcapFactor, + this.matcapTexture, + this.parametricRimColorFactor, + this.rimMultiplyTexture, + this.rimLightingMixFactor, + this.parametricRimFresnelPowerFactor, + this.parametricRimLiftFactor, + this.outlineWidthMode, + this.outlineWidthFactor, + this.outlineWidthMultiplyTexture, + this.outlineColorFactor, + this.outlineLightingMixFactor, + this.uvAnimationMaskTexture, + this.uvAnimationScrollXSpeedFactor, + this.uvAnimationScrollYSpeedFactor, + this.uvAnimationRotationSpeedFactor, + Map extensions, + Object extras) + : super(extensions, extras); + + static VrmcMaterialsMtoon fromMap(Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_NODE_CONSTRAINT_MEMBERS, context); + } + + final specVersion = getString(map, SPEC_VERSION, context, + list: VRMC_MATERIALS_MTOON_SPEC_VERSIONS, req: true); + + return VrmcMaterialsMtoon._( + specVersion, + getBool(map, TRANSPARENT_WITH_Z_WRITE, context), + getInt(map, RENDER_QUEUE_OFFSET_NUMBER, context, + min: -9, max: 9, def: 0, req: false), + getFloatList(map, SHADE_COLOR_FACTOR, context, + lengthsList: const [3], def: const [1, 1, 1]), + getObjectFromInnerMap( + map, SHADE_MULTIPLY_TEXTURE, context, TextureInfo.fromMap), + getFloat(map, SHADING_SHIFT_FACTOR, context), + getObjectFromInnerMap(map, SHADING_SHIFT_TEXTURE, context, + VrmcMaterialsMtoonShadingShiftTextureInfo.fromMap), + getFloat(map, SHADING_TOONY_FACTOR, context), + getFloat(map, GI_EQUALIZATION_FACTOR, context), + getFloatList(map, MATCAP_FACTOR, context, + lengthsList: const [3], def: const [1, 1, 1]), + getObjectFromInnerMap( + map, MATCAP_TEXTURE, context, TextureInfo.fromMap), + getFloatList(map, PARAMETRIC_RIM_COLOR_FACTOR, context, + lengthsList: const [3], def: const [1, 1, 1]), + getObjectFromInnerMap( + map, RIM_MULTIPLY_TEXTURE, context, TextureInfo.fromMap), + getFloat(map, RIM_LIGHTING_MIX_FACTOR, context), + getFloat(map, PARAMETRIC_RIM_FRESNEL_POWER_FACTOR, context), + getFloat(map, PARAMETRIC_RIM_LIFT_FACTOR, context), + getString(map, OUTLINE_WIDTH_MODE, context, + def: NONE, list: VRMC_MATERIALS_MTOON_OUTLINE_WIDTH_MODES), + getFloat(map, OUTLINE_WIDTH_FACTOR, context), + getObjectFromInnerMap( + map, OUTLINE_WIDTH_MULTIPLY_TEXTURE, context, TextureInfo.fromMap), + getFloatList(map, OUTLINE_COLOR_FACTOR, context, + lengthsList: const [3], def: const [1, 1, 1]), + getFloat(map, OUTLINE_LIGHTING_MIX_FACTOR, context), + getObjectFromInnerMap( + map, UV_ANIMATION_MASK_TEXTURE, context, TextureInfo.fromMap), + getFloat(map, UV_ANIMATION_SCROLL_X_SPEED_FACTOR, context), + getFloat(map, UV_ANIMATION_SCROLL_Y_SPEED_FACTOR, context), + getFloat(map, UV_ANIMATION_ROTATION_SPEED_FACTOR, context), + getExtensions(map, VrmcMaterialsMtoon, context), + getExtras(map, context)); + } + + void validateRenderQueueOffset(Gltf gltf, Context context) { + if (!context.validate) { + return; + } + + context.path.add(RENDER_QUEUE_OFFSET_NUMBER); + + if (renderQueueOffsetNumber == 0) { + // 0 is always valid + return; + } + + // now the alphaMode must be BLEND + // since the renderQueueOffsetNumber is not 0 + + Object o = this; + while (o != null) { + o = context.owners[o]; + if (o is Material) { + if (o.alphaMode != BLEND) { + context.addIssue(LinkError.vrmcMaterialsMtoonInvalidRenderQueueOffset, + args: [renderQueueOffsetNumber, ALPHA_MODE, o.alphaMode]); + } else if ((transparentWithZWrite && renderQueueOffsetNumber < 0) || + (!transparentWithZWrite && renderQueueOffsetNumber > 0)) { + context.addIssue(LinkError.vrmcMaterialsMtoonInvalidRenderQueueOffset, + args: [ + renderQueueOffsetNumber, + TRANSPARENT_WITH_Z_WRITE, + transparentWithZWrite + ]); + } + + break; + } + } + + context.path.removeLast(); + } + + @override + void link(Gltf gltf, Context context) { + validateRenderQueueOffset(gltf, context); + + context.path.add(SHADE_MULTIPLY_TEXTURE); + shadeMultiplyTexture?.link(gltf, context); + context.path.removeLast(); + + context.path.add(SHADING_SHIFT_TEXTURE); + shadingShiftTexture?.link(gltf, context); + context.path.removeLast(); + + context.path.add(MATCAP_TEXTURE); + matcapTexture?.link(gltf, context); + context.path.removeLast(); + + context.path.add(RIM_MULTIPLY_TEXTURE); + rimMultiplyTexture?.link(gltf, context); + context.path.removeLast(); + + context.path.add(OUTLINE_WIDTH_MULTIPLY_TEXTURE); + outlineWidthMultiplyTexture?.link(gltf, context); + context.path.removeLast(); + + context.path.add(UV_ANIMATION_MASK_TEXTURE); + uvAnimationMaskTexture?.link(gltf, context); + context.path.removeLast(); + } +} + +const Extension vrmcMaterialsMtoonExtension = + Extension(VRMC_MATERIALS_MTOON, { + Material: ExtensionDescriptor(VrmcMaterialsMtoon.fromMap), +}); diff --git a/lib/src/ext/VRMC_materials_mtoon/vrmc_materials_mtoon_shading_shift_texture_info.dart b/lib/src/ext/VRMC_materials_mtoon/vrmc_materials_mtoon_shading_shift_texture_info.dart new file mode 100644 index 0000000..2eb84d0 --- /dev/null +++ b/lib/src/ext/VRMC_materials_mtoon/vrmc_materials_mtoon_shading_shift_texture_info.dart @@ -0,0 +1,92 @@ +/* + * # Copyright (c) 2016-2019 The Khronos Group Inc. + * # + * # 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_materials_mtoon; + +import 'package:gltf/src/base/gltf_property.dart'; + +// shading shift texture info +// https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_materials_mtoon-1.0-beta/schema/mtoon.shadingShiftTexture.schema.json + +const String SCALE = 'scale'; + +const List VRMC_MATERIALS_MTOON_SHADING_SHIFT_TEXTURE_INFO_MEMBERS = + [ + INDEX, + TEX_COORD, + SCALE, +]; + +// I have tried to extend TextureInfo but I couldn't, +// It says that TextureInfo does not have a constructor named `_`. + +class VrmcMaterialsMtoonShadingShiftTextureInfo extends GltfProperty { + final int index; + final int texCoord; + final double scale; + + Texture texture; + + VrmcMaterialsMtoonShadingShiftTextureInfo._(this.index, this.texCoord, + this.scale, Map extensions, Object extras) + : super(extensions, extras); + + static VrmcMaterialsMtoonShadingShiftTextureInfo fromMap( + Map map, Context context) { + if (context.validate) { + checkMembers(map, VRMC_MATERIALS_MTOON_SHADING_SHIFT_TEXTURE_INFO_MEMBERS, + context); + } + + final extensions = getExtensions( + map, VrmcMaterialsMtoonShadingShiftTextureInfo, context, + overriddenType: Material); + + final shadingShiftTextureInfo = VrmcMaterialsMtoonShadingShiftTextureInfo._( + getIndex(map, INDEX, context), + getUint(map, TEX_COORD, context, def: 0), + getFloat(map, SCALE, context, def: 1), + extensions, + getExtras(map, context)); + + context.registerObjectsOwner(shadingShiftTextureInfo, extensions.values); + + return shadingShiftTextureInfo; + } + + @override + void link(Gltf gltf, Context context) { + texture = gltf.textures[index]; + + if (context.validate && index != -1) { + if (texture == null) { + context.addIssue(LinkError.unresolvedReference, + name: INDEX, args: [index]); + } else { + texture.markAsUsed(); + } + } + + Object o = this; + while (o != null) { + o = context.owners[o]; + if (o is Material) { + o.texCoordIndices[context.getPointerString()] = texCoord; + break; + } + } + } +} diff --git a/lib/src/ext/extensions.dart b/lib/src/ext/extensions.dart index 43f5ea2..4c4cd81 100644 --- a/lib/src/ext/extensions.dart +++ b/lib/src/ext/extensions.dart @@ -32,6 +32,7 @@ import 'package:gltf/src/ext/KHR_materials_variants/KHR_materials_variants.dart' 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'; @@ -115,6 +116,7 @@ const List kDefaultExtensions = [ khrMaterialsVolumeExtension, khrMeshQuantizationExtension, khrTextureTransformExtension, + vrmcMaterialsMtoonExtension, vrmcNodeConstraintExtension, vrmcSpringBoneExtension, vrmcVrmExtension, diff --git a/lib/src/utils.dart b/lib/src/utils.dart index cd5b38d..9798e0d 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -109,6 +109,33 @@ int getUint(Map map, String name, Context context, return -1; } +int getInt(Map map, String name, Context context, + {bool req = false, + int min = -9007199254740992, // TODO: make it a constant + int max = 9007199254740991, // TODO: make it a constant + int def = 0}) { + assert(min != null && max != null); + assert(max >= min); + final value = _tryFixInt(_getGuarded(map, name, _kInteger, context)); + if (value is int) { + if ((value < min) || (value > max)) { + context + .addIssue(SchemaError.valueNotInRange, name: name, args: [value]); + return 0; // TODO: low confidence. Float uses NaN, Uint uses -1 + } + return value; + } else if (value == null) { + if (!req) { + return def; + } + context.addIssue(SchemaError.undefinedProperty, args: [name]); + } else { + context.addIssue(SchemaError.typeMismatch, + name: name, args: [value, _kInteger]); + } + return -1; +} + double getFloat(Map map, String name, Context context, {bool req = false, double standalone = double.nan,