diff --git a/specification/2.1/Specification.adoc b/specification/2.1/Specification.adoc index 48d3649b30..9f54838790 100644 --- a/specification/2.1/Specification.adoc +++ b/specification/2.1/Specification.adoc @@ -845,6 +845,39 @@ The next example defines the transform for a node with attached camera using the ---- +[[bounding-volumes]] +=== Bounding Volumes + +Any glTF node **MAY** define a local space bounding volume using a node's `boundingVolume` property. A bounding volume is a simple geometric shape, optionally transformed, that **SHOULD** completely enclose the node's geometry. + +Bounding volumes contain the following properties: + +[options="header"] +|==== +| Property | Description +| `shape` | The index of the shape in the top-level `shapes` array. Required. +| `rotation` | The bounding volume's unit quaternion rotation relative to the node's local coordinate system. +| `scale` | The bounding volume's scale along the rotated local X, Y, and Z axes. +| `translation` | The bounding volume's translation along the node's local X, Y, and Z axes. +|==== + +Multiple bounding volumes defined in a node hierarchy form a bounding volume hierarchy (BVH), which can be used to accelerate frustum culling, occlusion culling, ray tracing, and so on. Bounding volume hierarchies **SHOULD** have spatial coherence, meaning that a node's bounding volume completely encloses the geometry of its descendants. + +Bounding volumes may be used to defer loading of content based on application-specific criteria, such as the angular size on a player's camera, or any other metric. Bounding volumes may be used to restrict rendering, interactions, or anything else to a specific volume of space. Implementations may choose to use bounding volumes for any purpose. + +The tightness or looseness of the bounding volume may be customized by the asset author depending on the use case. For example, a bounding volume used for a skinned mesh is **RECOMMENDED** to be large enough to contain the mesh in all reasonable deformation states, such as the possible poses of a character. Similarly, a bounding volume placed on a parent with multiple animated children is **RECOMMENDED** to be large enough to contain all of the children in all reasonable animation states. Alternatively, such a parent could simply not define a bounding volume, and dynamically rely on the bounding volumes of its children, if any. + +`"box"`` and `"sphere"` are the supported shape types for bounding volumes due to their simplicity and efficiency. Additional shape types **MAY** be allowed via extensions. + +Additionally, changing the shape parameters **SHOULD** be preferred over changing the bounding volume's `scale`. + +[NOTE] +.Implementation Note +==== +Many engines use axis-aligned bounding boxes (AABBs) for mesh culling. When converting from bounding volumes to AABBs programmatically, the resulting AABB should include all extents of the original bounding volume, including any transformations. Note that the transformations may be non-conformal (e.g. a transformation with a skew or a non-uniform scale). +==== + + [[binary-data-storage]] == Binary Data Storage @@ -2589,6 +2622,49 @@ See <> for additional information about int Skinned animation is achieved by animating the joints in the skin's joint hierarchy. +[[shapes]] +== Shapes + +glTF defines an array of shapes. All built-in shape types are "implicit" shapes, meaning that they have a surface defined by a mathematical function, and they have a distinct inside and outside. + +Similarly to other glTF objects, shapes in this array do not automatically have behavior. Other objects in the glTF document may use these shapes to instance them in the scene, providing the geometry data required for the behavior of that object, such as bounding volumes, or extension-defined physics colliders. + +Each shape is specified with a mandatory `type` property, and an additional structure with various size parameters depending on the type of shape. The following example defines a sphere shape with a radius of 0.1 meters. + +[source,json] +---- +{ + "shapes": [ + { + "sphere": { "radius": 0.1 }, + "type": "sphere" + } + ] +} +---- + + +The core glTF 2.1 specification includes the following shape types: + +[options="header"] +|==== +| `"box"` | An axis-aligned box with a size per-axis, centered at the origin in local space, with normals facing outwards along each axis. +| `"capsule"` | A capsule, centered at the origin in local space with potentially different radii at each end, equivalent to the convex hull of two spheres located along the Y axis (in local space) at a specified distance, with normals facing outwards. +| `"cylinder"` | A cylinder, centered at the origin in local space with potentially different radii at each end, equivalent to the convex hull of two circles in the X/Z plane positioned along the Y axis at a specified distance, with the top cap normal facing along the +Y axis, the bottom cap normal facing along the -Y axis, and the side normals facing outwards along the X/Z plane. +| `"plane"` | A plane centered at the origin in local space, optionally with finite extents, with normal along the +Y axis in local space. The plane is one-sided. +| `"sphere"` | A sphere, centered at the origin in local space with a radius equal in all directions, with normals facing outwards. +|==== + + +A sub-object with a key matching the shape type **MAY** be used to specify the size parameters of the shape. When the `type` of a shape refers to a built-in shape type, the shape must not supply a sub-object for a different shape type: for example, a shape whose `type` is "box" must not have a `sphere` property defined. + +By default, planes have infinite extents along their tangent and bitangent vectors, specified as X and Z in local space, respectively. A plane may optionally provide `sizeX` and `sizeZ` properties to specify a finite total size along these axes. The center of the plane's surface is at the origin in local space. The entirety of the space behind the plane, in its local -Y direction, is considered inside of the plane. + +Degenerate shapes are prohibited. A sphere must have a positive, non-zero radius. A box shape must have positive non-zero values for each component of `size`. The cylinder and capsule shapes must have a positive, non-zero `height`, both `radiusTop` and `radiusBottom` must be non-negative, and at least one of `radiusTop` and `radiusBottom` must be non-zero. A plane must have positive non-zero `sizeX` and `sizeZ` extents, if provided. + +Extensions **MAY** define additional shape types, in which case the `type` **MAY** be set to a custom value, or set to one of the built-in shape types to provide a fallback. + + [[specifying-extensions]] == Specifying Extensions diff --git a/specification/2.1/schema/boundingVolume.schema.json b/specification/2.1/schema/boundingVolume.schema.json new file mode 100644 index 0000000000..98510799a9 --- /dev/null +++ b/specification/2.1/schema/boundingVolume.schema.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "boundingVolume.schema.json", + "title": "Bounding Volume", + "type": "object", + "description": "A bounding volume.", + "allOf": [ { "$ref": "glTFProperty.schema.json" } ], + "properties": { + "shape": { + "allOf": [ { "$ref": "glTFid.schema.json" } ], + "description": "The index of the shape used for this bounding volume." + }, + "rotation": { + "type": "array", + "description": "The bounding volume's unit quaternion rotation relative to the node's local coordinate system.", + "items": { + "type": "number", + "minimum": -1.0, + "maximum": 1.0 + }, + "minItems": 4, + "maxItems": 4, + "default": [ 0.0, 0.0, 0.0, 1.0 ] + }, + "scale": { + "type": "array", + "description": "The bounding volume's scale along the rotated local X, Y, and Z axes.", + "items": { + "type": "number" + }, + "minItems": 3, + "maxItems": 3, + "default": [ 1.0, 1.0, 1.0 ] + }, + "translation": { + "type": "array", + "description": "The bounding volume's translation along the node's local X, Y, and Z axes.", + "items": { + "type": "number" + }, + "minItems": 3, + "maxItems": 3, + "default": [ 0.0, 0.0, 0.0 ] + } + }, + "required": [ "shape" ] +} \ No newline at end of file diff --git a/specification/2.1/schema/glTF.schema.json b/specification/2.1/schema/glTF.schema.json index e587e0106b..18ef7ec82a 100644 --- a/specification/2.1/schema/glTF.schema.json +++ b/specification/2.1/schema/glTF.schema.json @@ -129,6 +129,14 @@ }, "minItems": 1 }, + "shapes": { + "type": "array", + "description": "An array of shapes.", + "items": { + "$ref": "shape.schema.json" + }, + "minItems": 1 + }, "skins": { "type": "array", "description": "An array of skins.", diff --git a/specification/2.1/schema/node.schema.json b/specification/2.1/schema/node.schema.json index cb5946796f..06053e9a11 100644 --- a/specification/2.1/schema/node.schema.json +++ b/specification/2.1/schema/node.schema.json @@ -6,6 +6,10 @@ "description": "A node in the node hierarchy. When the node contains `skin`, all `mesh.primitives` **MUST** contain `JOINTS_0` and `WEIGHTS_0` attributes. A node **MAY** have either a `matrix` or any combination of `translation`/`rotation`/`scale` (TRS) properties. TRS properties are converted to matrices and postmultiplied in the `T * R * S` order to compose the transformation matrix; first the scale is applied to the vertices, then the rotation, and then the translation. If none are provided, the transform is the identity. When a node is targeted for animation (referenced by an animation.channel.target), `matrix` **MUST NOT** be present.", "allOf": [ { "$ref": "glTFChildOfRootProperty.schema.json" } ], "properties": { + "boundingVolume": { + "allOf": [ { "$ref": "boundingVolume.schema.json" } ], + "description": "The bounding volume of the node." + }, "camera": { "allOf": [ { "$ref": "glTFid.schema.json" } ], "description": "The index of the camera referenced by this node." diff --git a/specification/2.1/schema/shape.box.schema.json b/specification/2.1/schema/shape.box.schema.json new file mode 100644 index 0000000000..3e1f9d625b --- /dev/null +++ b/specification/2.1/schema/shape.box.schema.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "shape.box.schema.json", + "title": "glTF Box Shape", + "type": "object", + "description": "Parameters describing a box shape.", + "allOf": [ { "$ref": "glTFProperty.schema.json" } ], + "properties": { + "size": { + "type": "array", + "description": "The size (diameter) of the box in each axis in local space.", + "items": { + "type": "number", + "exclusiveMinimum": 0.0 + }, + "minItems": 3, + "maxItems": 3, + "default": [ 1.0, 1.0, 1.0 ] + } + } +} diff --git a/specification/2.1/schema/shape.capsule.schema.json b/specification/2.1/schema/shape.capsule.schema.json new file mode 100644 index 0000000000..952d72e3be --- /dev/null +++ b/specification/2.1/schema/shape.capsule.schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "shape.capsule.schema.json", + "title": "glTF Capsule Shape", + "type": "object", + "description": "Parameters describing a capsule shape.", + "allOf": [ { "$ref": "glTFProperty.schema.json" } ], + "properties": { + "height": { + "type": "number", + "description": "The distance between the centers of the two capping spheres of capsule.", + "exclusiveMinimum": 0.0, + "default": 1.0 + }, + "radiusBottom": { + "type": "number", + "description": "The radius of the sphere located at the bottom of the capsule (i.e. the sphere at the half-height along -Y)", + "minimum": 0.0, + "default": 0.5 + }, + "radiusTop": { + "type": "number", + "description": "The radius of the sphere located at the top of the capsule (i.e. the sphere at the half-height along +Y)", + "minimum": 0.0, + "default": 0.5 + } + } +} diff --git a/specification/2.1/schema/shape.cylinder.schema.json b/specification/2.1/schema/shape.cylinder.schema.json new file mode 100644 index 0000000000..1ba0c123d4 --- /dev/null +++ b/specification/2.1/schema/shape.cylinder.schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "shape.cylinder.schema.json", + "title": "glTF Cylinder Shape", + "type": "object", + "description": "Parameters describing a cylinder shape.", + "allOf": [ { "$ref": "glTFProperty.schema.json" } ], + "properties": { + "height": { + "type": "number", + "description": "The height of the cylinder, centered along the Y axis.", + "exclusiveMinimum": 0.0, + "default": 2.0 + }, + "radiusBottom": { + "type": "number", + "description": "The radius of the bottom of the cylinder (the disc located along -Y.)", + "minimum": 0.0, + "default": 0.5 + }, + "radiusTop": { + "type": "number", + "description": "The radius of the top of the cylinder (the disc located along +Y.)", + "minimum": 0.0, + "default": 0.5 + } + } +} diff --git a/specification/2.1/schema/shape.plane.schema.json b/specification/2.1/schema/shape.plane.schema.json new file mode 100644 index 0000000000..eb90480b99 --- /dev/null +++ b/specification/2.1/schema/shape.plane.schema.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "shape.plane.schema.json", + "title": "glTF Plane Shape", + "type": "object", + "description": "Parameters describing a plane shape.", + "allOf": [ { "$ref": "glTFProperty.schema.json" } ], + "properties": { + "sizeX": { + "type": "number", + "description": "The extents along the X axis in local space. If not provided, this dimension should be considered infinite.", + "exclusiveMinimum": 0.0 + }, + "sizeZ": { + "type": "number", + "description": "The extents along the Z axis in local space. If not provided, this dimension should be considered infinite.", + "exclusiveMinimum": 0.0 + } + } +} diff --git a/specification/2.1/schema/shape.schema.json b/specification/2.1/schema/shape.schema.json new file mode 100644 index 0000000000..fbc503c975 --- /dev/null +++ b/specification/2.1/schema/shape.schema.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "shape.schema.json", + "title": "glTF Shape Resource", + "type": "object", + "description": "Parameters describing a shape used for physics, BVH, or any other use case.", + "allOf": [ { "$ref": "glTFChildOfRootProperty.schema.json" } ], + "properties": { + "type": { + "description": "Specifies the shape type.", + "anyOf": [ + { + "const": "box", + "description": "An axis-aligned box with a size per-axis, centered at the origin in local space, with normals facing outwards along each axis." + }, + { + "const": "capsule", + "description": "A capsule, centered at the origin in local space with potentially different radii at each end, equivalent to the convex hull of two spheres located along the Y axis (in local space) at a specified distance, with normals facing outwards." + }, + { + "const": "cylinder", + "description": "A cylinder, centered at the origin in local space with potentially different radii at each end, equivalent to the convex hull of two circles in the X/Z plane positioned along the Y axis at a specified distance, with the top cap normal facing along the +Y axis, the bottom cap normal facing along the -Y axis, and the side normals facing outwards along the X/Z plane." + }, + { + "const": "plane", + "description": "A plane centered at the origin in local space, optionally with finite extents, with normal along the +Y axis in local space. The plane is one-sided." + }, + { + "const": "sphere", + "description": "A sphere, centered at the origin in local space with a radius equal in all directions, with normals facing outwards." + }, + { + "type": "string" + } + ] + }, + "box": { + "type": "object", + "description": "A set of parameter values that are used to define a box shape.", + "$ref": "shape.box.schema.json" + }, + "capsule": { + "type": "object", + "description": "A set of parameter values that are used to define a capsule shape.", + "$ref": "shape.capsule.schema.json" + }, + "cylinder": { + "type": "object", + "description": "A set of parameter values that are used to define a cylinder shape.", + "$ref": "shape.cylinder.schema.json" + }, + "plane": { + "type": "object", + "description": "A set of parameter values that are used to define a plane shape.", + "$ref": "shape.plane.schema.json" + }, + "sphere": { + "type": "object", + "description": "A set of parameter values that are used to define a sphere shape.", + "$ref": "shape.sphere.schema.json" + } + }, + "required": [ + "type" + ] +} diff --git a/specification/2.1/schema/shape.sphere.schema.json b/specification/2.1/schema/shape.sphere.schema.json new file mode 100644 index 0000000000..cc86782b6e --- /dev/null +++ b/specification/2.1/schema/shape.sphere.schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "shape.sphere.schema.json", + "title": "glTF Sphere Shape", + "type": "object", + "description": "Parameters describing a sphere shape.", + "allOf": [ { "$ref": "glTFProperty.schema.json" } ], + "properties": { + "radius": { + "type": "number", + "description": "The radius of the sphere.", + "exclusiveMinimum": 0.0, + "default": 0.5 + } + } +}