diff --git a/lib/src/errors.dart b/lib/src/errors.dart index fa6477b..0199744 100644 --- a/lib/src/errors.dart +++ b/lib/src/errors.dart @@ -554,6 +554,20 @@ class SemanticError extends IssueType { 'interior volume and the trigger may not work as expected.', Severity.Information); + static final SemanticError omiPhysicsBodyInvalidNode = + SemanticError._( + 'OMI_PHYSICS_BODY_INVALID_NODE', + (args) => 'A physics body must be on its own node, it cannot be ' + 'on a node with other components such as a mesh or camera.', + Severity.Error); + + static final SemanticError omiPhysicsBodyMissingCollider = + SemanticError._( + 'OMI_PHYSICS_BODY_MISSING_COLLIDER', + (args) => 'This physics body does not have any colliders. ' + 'This is valid but body will not collide with anything.', + Severity.Information); + SemanticError._(String type, ErrorFunction message, [Severity severity = Severity.Error]) : super(type, message, severity); diff --git a/lib/src/ext/OMI_physics_body/omi_physics_body.dart b/lib/src/ext/OMI_physics_body/omi_physics_body.dart new file mode 100644 index 0000000..4f573f3 --- /dev/null +++ b/lib/src/ext/OMI_physics_body/omi_physics_body.dart @@ -0,0 +1,117 @@ +// Copyright 2022 The Khronos Group Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +library gltf.extensions.omi_physics_body; + +import 'package:gltf/src/base/gltf_property.dart'; +import 'package:gltf/src/ext/extensions.dart'; + +const String OMI_PHYSICS_BODY = 'OMI_physics_body'; + +const String MASS = 'mass'; +const String LINEAR_VELOCITY = 'linearVelocity'; +const String ANGULAR_VELOCITY = 'angularVelocity'; + +const String STATIC = 'static'; +const String KINEMATIC = 'kinematic'; +const String CHARACTER = 'character'; +const String RIGID = 'rigid'; +const String VEHICLE = 'vehicle'; +const String TRIGGER = 'trigger'; + +const List OMI_PHYSICS_BODY_MEMBERS = [ + TYPE, + MASS, + LINEAR_VELOCITY, + ANGULAR_VELOCITY +]; + +const List OMI_PHYSICS_BODY_TYPES = [ + STATIC, + KINEMATIC, + CHARACTER, + RIGID, + VEHICLE, + TRIGGER +]; + +class OmiPhysicsBody extends GltfProperty { + final String type; + final double mass; + final List linearVelocity; + final List angularVelocity; + + OmiPhysicsBody._(this.type, this.mass, this.linearVelocity, + this.angularVelocity, Map extensions, Object extras) + : super(extensions, extras); + + static OmiPhysicsBody fromMap(Map map, Context context) { + if (context.validate) { + checkMembers(map, OMI_PHYSICS_BODY_MEMBERS, context); + } + + final type = getString(map, TYPE, context, + list: OMI_PHYSICS_BODY_TYPES, req: true); + + final mass = getFloat(map, MASS, context, def: 1); + + final linearVelocity = getFloatList(map, LINEAR_VELOCITY, context, + lengthsList: const [3], def: const [0.0, 0.0, 0.0]); + + final angularVelocity = getFloatList(map, ANGULAR_VELOCITY, context, + lengthsList: const [3], def: const [0.0, 0.0, 0.0]); + + return OmiPhysicsBody._( + type, + mass, + linearVelocity, + angularVelocity, + getExtensions(map, OmiPhysicsBody, context), + getExtras(map, context)); + } + + @override + void link(Gltf gltf, Context context) { + if (!context.validate) { + return; + } + // Get the glTF node that this physics body is attached to. + final path = context.path; + if (path.length < 2 || path[0] != 'nodes') { + return; + } + final nodeIndex = int.tryParse(path[1]); + if (nodeIndex == null) { + return; + } + final node = gltf.nodes[nodeIndex]; + // Ensure that the physics body is not on the same node as a mesh or camera. + if (node.mesh != null) { + context.addIssue(SemanticError.omiPhysicsBodyInvalidNode); + } + if (node.camera != null) { + context.addIssue(SemanticError.omiPhysicsBodyInvalidNode); + } + // Check that the physics body has at least one collider. + var hasCollider = false; + if (node.children != null) { + for (final child in node.children) { + if (child.extensions != null && + child.extensions.containsKey(OMI_COLLIDER)) { + hasCollider = true; + break; + } + } + } + if (!hasCollider) { + context.addIssue(SemanticError.omiPhysicsBodyMissingCollider, + name: OMI_PHYSICS_BODY); + } + } +} + +const Extension omiPhysicsBodyExtension = Extension( + OMI_PHYSICS_BODY, { + Node: ExtensionDescriptor(OmiPhysicsBody.fromMap) +}); diff --git a/lib/src/ext/extensions.dart b/lib/src/ext/extensions.dart index 0f418a4..abd35e6 100644 --- a/lib/src/ext/extensions.dart +++ b/lib/src/ext/extensions.dart @@ -33,6 +33,7 @@ import 'package:gltf/src/ext/KHR_materials_volume/khr_materials_volume.dart'; import 'package:gltf/src/ext/KHR_mesh_quantization/khr_mesh_quantization.dart'; import 'package:gltf/src/ext/KHR_texture_transform/khr_texture_transform.dart'; import 'package:gltf/src/ext/OMI_collider/omi_collider.dart'; +import 'package:gltf/src/ext/OMI_physics_body/omi_physics_body.dart'; import 'package:gltf/src/hash.dart'; import 'package:meta/meta.dart'; @@ -53,6 +54,7 @@ export 'package:gltf/src/ext/KHR_materials_volume/khr_materials_volume.dart'; export 'package:gltf/src/ext/KHR_mesh_quantization/khr_mesh_quantization.dart'; export 'package:gltf/src/ext/KHR_texture_transform/khr_texture_transform.dart'; export 'package:gltf/src/ext/OMI_collider/omi_collider.dart'; +export 'package:gltf/src/ext/OMI_physics_body/omi_physics_body.dart'; class Extension { const Extension(this.name, this.functions, @@ -114,7 +116,8 @@ const List kDefaultExtensions = [ khrMaterialsVolumeExtension, khrMeshQuantizationExtension, khrTextureTransformExtension, - omiColliderExtension + omiColliderExtension, + omiPhysicsBodyExtension ]; // https://github.com/KhronosGroup/glTF/blob/main/extensions/Prefixes.md