diff --git a/lib/src/errors.dart b/lib/src/errors.dart index 4e575673..f547fb62 100644 --- a/lib/src/errors.dart +++ b/lib/src/errors.dart @@ -533,6 +533,20 @@ 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); @@ -780,6 +794,46 @@ 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])}.'); + + static final LinkError vrmcVrmExpressionsNoTargetMorph = LinkError._( + 'VRMC_VRM_EXPRESSIONS_NO_TARGET_MORPH', + (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 vrmcVrmFirstPersonMeshAnnotationOverride = + LinkError._( + '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); + + 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 9129fa9e..d2c2f441 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 00000000..cf5478b3 --- /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 00000000..2eb84d00 --- /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/VRMC_node_constraint/vrmc_node_constraint.dart b/lib/src/ext/VRMC_node_constraint/vrmc_node_constraint.dart new file mode 100644 index 00000000..aec099ea --- /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_NODE_CONSTRAINT_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_NODE_CONSTRAINT_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 00000000..a2478312 --- /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 00000000..8fc00a94 --- /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 00000000..eb8c038c --- /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 00000000..74652468 --- /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/VRMC_springBone/vrmc_spring_bone.dart b/lib/src/ext/VRMC_springBone/vrmc_spring_bone.dart new file mode 100644 index 00000000..5b9a77ae --- /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_SPRING_BONE_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_SPRING_BONE_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 00000000..98a0d4cf --- /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 00000000..38372680 --- /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 00000000..8656a50b --- /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 00000000..e19064e9 --- /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 00000000..fc2e6804 --- /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/VRMC_vrm/vrmc_vrm.dart b/lib/src/ext/VRMC_vrm/vrmc_vrm.dart new file mode 100644 index 00000000..43fc894e --- /dev/null +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm.dart @@ -0,0 +1,120 @@ +/* + * # 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_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'; + +const String VRMC_VRM = 'VRMC_vrm'; +const String SPEC_VERSION = 'specVersion'; +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, + META, + HUMANOID, + EXPRESSIONS, + FIRST_PERSON, + LOOK_AT, +]; + +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; + final VrmcVrmExpressions expressions; + final VrmcVrmFirstPerson firstPerson; + 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) { + 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), + getObjectFromInnerMap( + map, EXPRESSIONS, context, VrmcVrmExpressions.fromMap, req: false), + getObjectFromInnerMap( + map, FIRST_PERSON, context, VrmcVrmFirstPerson.fromMap, req: false), + getObjectFromInnerMap(map, LOOK_AT, context, VrmcVrmLookAt.fromMap, + req: false), + getExtensions(map, VrmcVrm, context), + getExtras(map, context)); + } + + @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(); + + context.path.add(LOOK_AT); + lookAt?.link(gltf, context); + context.path.removeLast(); + } +} + +const Extension vrmcVrmExtension = + Extension(VRMC_VRM, { + Gltf: ExtensionDescriptor(VrmcVrm.fromMap), +}); 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 00000000..9f3539bb --- /dev/null +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm_expressions.dart @@ -0,0 +1,554 @@ +/* + * # 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(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, + 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) { + 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(); + + 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(); + + 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) { + // 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) { + 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(); + + context.path.add(CUSTOM); + custom.forEach((name, expression) { + context.path.add(name); + expression.link(gltf, context); + context.path.removeLast(); + }); + context.path.removeLast(); + } +} 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 00000000..97542a92 --- /dev/null +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm_first_person.dart @@ -0,0 +1,174 @@ +/* + * # 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)); + } + + 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 (var i = 0; i < meshAnnotations.length; i++) { + context.path.add(i.toString()); + meshAnnotations[i].link(gltf, context); + context.path.removeLast(); + } + 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 new file mode 100644 index 00000000..502d0a56 --- /dev/null +++ b/lib/src/ext/VRMC_vrm/vrmc_vrm_humanoid.dart @@ -0,0 +1,500 @@ +/* + * # 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 NODE = 'node'; + +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); + } + + return VrmcVrmHumanoidHumanBone._(getIndex(map, NODE, context, req: true), + 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) { + final bone = getObjectFromInnerMap( + map, name, context, VrmcVrmHumanoidHumanBone.fromMap, + req: VRMC_VRM_HUMANOID_HUMAN_BONES_REQUIRED[name]); + + if (bone != null) { + bones[name] = bone; + } + } + + 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) { + 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.vrmcVrmHumanoidInvalidHierarchy, + 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.vrmcVrmHumanoidInvalidHierarchy, + name: name, args: [name, ancestorName]); + } + + @override + void link(Gltf gltf, Context context) { + validateDuplicates(context); + + bones.forEach((name, bone) { + bone?.link(gltf, context); + validateHierarchy(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); + } + + return VrmcVrmHumanoid._( + getObjectFromInnerMap( + map, HUMAN_BONES, context, VrmcVrmHumanoidHumanBones.fromMap, + req: true), + getExtensions(map, VrmcVrmHumanoid, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + context.path.add(HUMAN_BONES); + humanBones?.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 00000000..c98664ca --- /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); + } +} 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 00000000..dda48e40 --- /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 4acdc044..4c4cd813 100644 --- a/lib/src/ext/extensions.dart +++ b/lib/src/ext/extensions.dart @@ -32,6 +32,10 @@ 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'; import 'package:gltf/src/hash.dart'; import 'package:meta/meta.dart'; @@ -111,7 +115,11 @@ const List kDefaultExtensions = [ khrMaterialsVariantsExtension, khrMaterialsVolumeExtension, khrMeshQuantizationExtension, - khrTextureTransformExtension + khrTextureTransformExtension, + vrmcMaterialsMtoonExtension, + vrmcNodeConstraintExtension, + vrmcSpringBoneExtension, + vrmcVrmExtension, ]; // https://github.com/KhronosGroup/glTF/blob/main/extensions/Prefixes.md diff --git a/lib/src/utils.dart b/lib/src/utils.dart index cd5b38d3..9798e0d8 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,