Skip to content

Commit

Permalink
Add OMI_collider extension validation
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronfranke committed Dec 10, 2022
1 parent 2b8bd0f commit ef53adc
Show file tree
Hide file tree
Showing 3 changed files with 299 additions and 1 deletion.
29 changes: 29 additions & 0 deletions lib/src/errors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,35 @@ class SemanticError extends IssueType {
'minimum is equal to the thickness maximum.',
Severity.Information);

static final SemanticError omiColliderInvalidCapsuleHeight =
SemanticError._(
'OMI_COLLIDER_INVALID_CAPSULE_HEIGHT',
(args) => 'The capsule height must be at least twice the radius.',
Severity.Error);

static final SemanticError omiColliderInvalidHullPoints =
SemanticError._(
'OMI_COLLIDER_INVALID_HULL_POINTS',
(args) => 'The hull points must contain at least one point. '
'Each point is a 3D vector made of 3 numbers, therefore '
'the size of the points array must be a multiple of 3.',
Severity.Error);

static final SemanticError omiColliderInvalidNode =
SemanticError._(
'OMI_COLLIDER_INVALID_NODE',
(args) => 'A collider 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 omiColliderTrimeshTrigger =
SemanticError._(
'OMI_COLLIDER_TRIMESH_TRIGGER',
(args) => 'This collider is both a trimesh and a trigger. This '
'is valid but not recommended since trimeshes do not have an '
'interior volume and the trigger may not work as expected.',
Severity.Information);

SemanticError._(String type, ErrorFunction message,
[Severity severity = Severity.Error])
: super(type, message, severity);
Expand Down
266 changes: 266 additions & 0 deletions lib/src/ext/OMI_collider/omi_collider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
// Copyright 2022 The Khronos Group Inc.
//
// SPDX-License-Identifier: Apache-2.0

library gltf.extensions.omi_collider;

import 'package:gltf/src/base/gltf_property.dart';
import 'package:gltf/src/ext/extensions.dart';

const String OMI_COLLIDER = 'OMI_collider';

const String IS_TRIGGER = 'isTrigger';
const String SIZE = 'size';
const String RADIUS = 'radius';
const String HEIGHT = 'height';
const String POINTS = 'points';
const String MESH = 'mesh';

const String BOX = 'box';
const String SPHERE = 'sphere';
const String CAPSULE = 'capsule';
const String CYLINDER = 'cylinder';
const String HULL = 'hull';
const String TRIMESH = 'trimesh';

const String COLLIDER = 'collider';
const String COLLIDERS = 'colliders';

const List<String> OMI_COLLIDER_GLTF_MEMBERS = <String>[COLLIDERS];
const List<String> OMI_COLLIDER_NODE_MEMBERS = <String>[COLLIDER];

const List<String> OMI_COLLIDER_COLLIDER_MEMBERS = <String>[
TYPE,
IS_TRIGGER,
SIZE,
RADIUS,
HEIGHT,
POINTS,
MESH
];

const List<String> OMI_COLLIDER_COLLIDER_TYPES = <String>[
BOX,
SPHERE,
CAPSULE,
CYLINDER,
HULL,
TRIMESH
];

const Extension omiColliderExtension =
Extension(OMI_COLLIDER, <Type, ExtensionDescriptor>{
Gltf: ExtensionDescriptor(OmiColliderGltf.fromMap),
Node: ExtensionDescriptor(OmiColliderNode.fromMap)
});

// The document-level extension that holds a list of colliders.
class OmiColliderGltf extends GltfProperty {
final SafeList<OmiColliderCollider> colliders;

OmiColliderGltf._(
this.colliders, Map<String, Object> extensions, Object extras)
: super(extensions, extras);

static OmiColliderGltf fromMap(
Map<String, Object> map, Context context) {
if (context.validate) {
checkMembers(map, OMI_COLLIDER_GLTF_MEMBERS, context);
}

SafeList<OmiColliderCollider> colliders;
final colliderMaps = getMapList(map, COLLIDERS, context);
if (colliderMaps != null) {
colliders = SafeList<OmiColliderCollider>(colliderMaps.length, COLLIDERS);
context.path.add(COLLIDERS);
for (var i = 0; i < colliderMaps.length; i++) {
final colliderMap = colliderMaps[i];
context.path.add(i.toString());
colliders[i] = OmiColliderCollider.fromMap(colliderMap, context);
context.path.removeLast();
}
context.path.removeLast();
} else {
colliders = SafeList<OmiColliderCollider>.empty(COLLIDERS);
}

return OmiColliderGltf._(
colliders,
getExtensions(map, OmiColliderGltf, context),
getExtras(map, context));
}

@override
void link(Gltf gltf, Context context) {
if (colliders != null) {
context.path.add(COLLIDERS);
final extCollectionList = context.path.toList(growable: false);
context.extensionCollections[colliders] = extCollectionList;
colliders.forEachWithIndices((i, collider) {
context.path.add(i.toString());
collider.link(gltf, context);
context.path.removeLast();
});
context.path.removeLast();
}
}
}

// The main data structure that stores collider data.
class OmiColliderCollider extends GltfChildOfRootProperty {
final String type;
final bool isTrigger;
final List<double> size;
final double radius;
final double height;
final List<double> points;
final int mesh;

OmiColliderCollider._(this.type, this.isTrigger, this.size, this.radius,
this.height, this.points, this.mesh, String name,
Map<String, Object> extensions, Object extras)
: super(name, extensions, extras);

static OmiColliderCollider fromMap(
Map<String, Object> map, Context context) {
if (context.validate) {
checkMembers(map, OMI_COLLIDER_COLLIDER_MEMBERS, context);
}

final type = getString(map, TYPE, context,
list: OMI_COLLIDER_COLLIDER_TYPES, req: true);

final isTrigger = getBool(map, IS_TRIGGER, context);

final size = getFloatList(map, SIZE, context,
lengthsList: const [3], min: 0, def: const [1.0, 1.0, 1.0]);

final radius = getFloat(map, RADIUS, context, min: 0, def: 0.5);
final height = getFloat(map, HEIGHT, context, min: 0, def: 2);

final points = getFloatList(map, POINTS, context, def: const []);

final mesh = getIndex(map, MESH, context, req: false);

if (context.validate) {
if (map.containsKey(SIZE) && type != BOX) {
context.addIssue(SemanticError.extraProperty, name: SIZE);
}
if (type != CAPSULE && type != CYLINDER) {
if (map.containsKey(HEIGHT)) {
context.addIssue(SemanticError.extraProperty, name: HEIGHT);
}
if (type != SPHERE) {
if (map.containsKey(RADIUS)) {
context.addIssue(SemanticError.extraProperty, name: RADIUS);
}
}
}
if (type == CAPSULE && height < radius * 2) {
context.addIssue(SemanticError.omiColliderInvalidCapsuleHeight,
name: HEIGHT);
}
if (map.containsKey(POINTS) && type != HULL) {
context.addIssue(SemanticError.extraProperty, name: POINTS);
}
if (type == HULL && (points.length < 3 || points.length % 3 != 0)) {
context.addIssue(SemanticError.omiColliderInvalidHullPoints,
name: POINTS);
}
if (map.containsKey(MESH) && type != TRIMESH) {
context.addIssue(SemanticError.extraProperty, name: MESH);
}
if (type == TRIMESH && isTrigger) {
context.addIssue(SemanticError.omiColliderTrimeshTrigger,
name: IS_TRIGGER);
}
}

return OmiColliderCollider._(
type,
isTrigger,
size,
radius,
height,
points,
mesh,
getName(map, context),
getExtensions(map, OmiColliderCollider, context),
getExtras(map, context));
}
}

// The node-level extension that references a document-level collider by index.
class OmiColliderNode extends GltfProperty {
final int _colliderIndex;

OmiColliderCollider _collider;

OmiColliderNode._(
this._colliderIndex, Map<String, Object> extensions, Object extras)
: super(extensions, extras);

static OmiColliderNode fromMap(
Map<String, Object> map, Context context) {
if (context.validate) {
checkMembers(map, OMI_COLLIDER_NODE_MEMBERS, context);
}

return OmiColliderNode._(
getIndex(map, COLLIDER, context),
getExtensions(map, OmiColliderNode, context),
getExtras(map, context));
}

@override
void link(Gltf gltf, Context context) {
if (!context.validate) {
return;
}
final collidersExtension = gltf.extensions[omiColliderExtension.name];
if (collidersExtension is OmiColliderGltf) {
// Mark the collider that this node references as used.
if (_colliderIndex != -1) {
_collider = collidersExtension.colliders[_colliderIndex];
if (_collider == null) {
context.addIssue(LinkError.unresolvedReference,
name: COLLIDER, args: [_colliderIndex]);
} else {
_collider.markAsUsed();
// If this is a trimesh, we also need to mark the mesh as used.
final meshIndex = _collider.mesh;
if (meshIndex != -1) {
if (meshIndex >= gltf.meshes.length) {
context.addIssue(LinkError.unresolvedReference,
name: MESH, args: [meshIndex]);
} else {
gltf.meshes[meshIndex].markAsUsed();
}
}
}
}
// Get the glTF node that this collider 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 collider is not on the same node as a mesh or camera.
if (node.mesh != null) {
context.addIssue(SemanticError.omiColliderInvalidNode, name: MESH);
}
if (node.camera != null) {
context.addIssue(SemanticError.omiColliderInvalidNode, name: CAMERA);
}
} else {
context.addIssue(SchemaError.unsatisfiedDependency,
args: ['/$EXTENSIONS/${omiColliderExtension.name}']);
}
}

OmiColliderCollider get collider => _collider;
}
5 changes: 4 additions & 1 deletion lib/src/ext/extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import 'package:gltf/src/ext/KHR_materials_variants/KHR_materials_variants.dart'
import 'package:gltf/src/ext/KHR_materials_volume/khr_materials_volume.dart';
import 'package:gltf/src/ext/KHR_mesh_quantization/khr_mesh_quantization.dart';
import 'package:gltf/src/ext/KHR_texture_transform/khr_texture_transform.dart';
import 'package:gltf/src/ext/OMI_collider/omi_collider.dart';
import 'package:gltf/src/hash.dart';
import 'package:meta/meta.dart';

Expand All @@ -51,6 +52,7 @@ export 'package:gltf/src/ext/KHR_materials_variants/KHR_materials_variants.dart'
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';

class Extension {
const Extension(this.name, this.functions,
Expand Down Expand Up @@ -111,7 +113,8 @@ const List<Extension> kDefaultExtensions = <Extension>[
khrMaterialsVariantsExtension,
khrMaterialsVolumeExtension,
khrMeshQuantizationExtension,
khrTextureTransformExtension
khrTextureTransformExtension,
omiColliderExtension
];

// https:/KhronosGroup/glTF/blob/main/extensions/Prefixes.md
Expand Down

0 comments on commit ef53adc

Please sign in to comment.