Skip to main content

Shape

Struct Shape 

Source
pub struct Shape(/* private fields */);
Expand description

A collision shape that bodies are created from.

Owns one Jolt reference. Every body created from it holds its own reference, so the shape may be dropped while bodies use it. Jolt shapes cannot change after construction, so one shape may serve any number of bodies in any number of worlds. To change a compound at run time, edit a MutableCompound and install the shapes it publishes.

Implementations§

Source§

impl Shape

Source

pub fn new_box_with_material( half_extent: Vec3, convex_radius: f32, material: &PhysicsMaterial, ) -> Result<Self, ShapeError>

new_box_with_convex_radius made of material; the same rules apply.

Source

pub fn new_sphere_with_material( radius: f32, material: &PhysicsMaterial, ) -> Result<Self, ShapeError>

new_sphere made of material; the same rules apply.

Source

pub fn new_capsule_with_material( half_height_of_cylinder: f32, radius: f32, material: &PhysicsMaterial, ) -> Result<Self, ShapeError>

new_capsule made of material; the same rules apply.

Source

pub fn new_cylinder_with_material( half_height: f32, radius: f32, convex_radius: f32, material: &PhysicsMaterial, ) -> Result<Self, ShapeError>

new_cylinder_with_convex_radius made of material; the same rules apply.

Source§

impl Shape

Source

pub fn collide_point(&self, point: Vec3) -> Result<Vec<SubShapeId>, QueryError>

The sub-shape ids of every leaf of this shape that contains point, given in the shape’s own frame (the frame a body puts at its position), sorted; empty when none does.

What “contains” means is Jolt’s per shape (Shape::CollidePoint):

  • box, sphere: the solid shape, boundary included (a box’s convex radius is ignored);
  • capsule, cylinder, tapered cylinder, convex hull: the solid shape (sharp edges for cylinders and hulls); points within about 1e-4 m of the surface may go either way;
  • tapered capsule: within about 1e-4 m of the rounded shape;
  • mesh: the point lies in the shape’s bounds and a ray from it along +Y crosses an odd number of triangles (the id is the last triangle crossed). Jolt does not check that the mesh is closed: an open mesh reports points whose ray happens to cross an odd number of triangles. A ray through an edge or a vertex counts every triangle that meets there, so such a point can come out on the wrong side even for a closed mesh: the centre of a cube mesh whose top face is split along a diagonal is outside. Every other point of a closed mesh comes out as the mesh encloses it;
  • heightfield: never;
  • plane: strictly behind the plane (normal · p + constant < 0), anywhere, also beyond the half extent, unlike a ray, which counts the plane itself as solid;
  • compound: each child whose bounds contain the point is asked (so a plane child is only found within its bounds); scaled, rotated-translated and offset shapes ask their inner shape.

Each component of point must be finite and at most limits::MAX_SHAPE_EXTENT in absolute value; otherwise QueryError::InvalidValue is returned.

Source§

impl Shape

Source

pub fn save_binary_state(&self) -> Result<Vec<u8>, ShapeError>

Saves the shape with its children and materials to bytes that restore_binary_state turns back into an equal shape (Jolt’s cooked shape data, Shape::SaveBinaryState per shape). Build large meshes once, at asset-build time, save them, and restore them when a level loads: restoring copies Jolt’s built data instead of building it again.

Every shape kind this crate builds is supported. A child or material shared by several parents is saved once and shared again after restoring; a mesh keeps its MeshSettings::max_convex_extent, compound children their user data, and PhysicsMaterials their user data. Two saves of equal shapes give equal bytes. The bytes start with a header that names this build (format version, Jolt and joltc commits, precision, determinism mode, byte order) and a checksum; the layout is in docs/shape-cooking.md.

§Errors

ShapeError::BinaryState with BinaryStateError::Rejected when joltc refuses a shape or material kind it cannot save; shapes and materials made by this crate are all supported.

§Example
use oxijolt::prelude::math::Vec3;
use oxijolt::Shape;

let crate_box = Shape::new_box(Vec3::new(0.5, 0.5, 0.5))?;
let bytes = crate_box.save_binary_state()?;
// SAFETY: the bytes were saved just above by this build and not changed.
let restored = unsafe { Shape::restore_binary_state(&bytes) }?;
assert_eq!(restored.save_binary_state()?, bytes);
Source

pub unsafe fn restore_binary_state(bytes: &[u8]) -> Result<Shape, ShapeError>

Restores a shape from bytes written by save_binary_state.

The header must name this build, and the checksum must match, before anything reaches Jolt. joltc then checks each record’s envelope (type, lengths, child and material indices) before Jolt reads the record, and the record’s child and material counts after Jolt read it, before they are attached. After restoring, the shape’s local bounds must lie within limits::MAX_SHAPE_EXTENT and a compound must fit Jolt’s sub-shape ids and limits::MAX_EXPANDED_SUB_SHAPES, the rules Shape::new_compound applies.

§Safety

bytes are the unchanged output of save_binary_state of a build with the same build id, in any process; they may have been stored or sent on the way. The header, the checksum and joltc’s record checks refuse bytes of another build, truncated bytes and most damage with an error, but passing them does not make other bytes valid: Jolt does not validate the inside of its own records (array lengths, mesh tree offsets, hull indices), so changed bytes that pass, whether damaged or crafted, can make Jolt read and write out of bounds. Do not restore bytes from a source you do not trust, such as another player.

§Errors

ShapeError::BinaryState with:

ShapeError::InitFailed when Jolt could not be initialised.

Source§

impl Shape

Source

pub fn new_convex_hull(points: &[Vec3]) -> Result<Self, ShapeError>

The convex hull of points (shape space, metres) with Jolt’s default convex radius, 0.05 m; otherwise as new_convex_hull_with_convex_radius.

Source

pub fn new_convex_hull_with_convex_radius( points: &[Vec3], convex_radius: f32, ) -> Result<Self, ShapeError>

The convex hull of points (shape space, metres) with a convex radius in metres.

Needs at least 4 points (ConvexHullError::TooFewPoints), each finite with every component at most limits::MAX_SHAPE_EXTENT in absolute value, and a convex radius that is finite and not negative (ShapeError::InvalidValue). Points on or close to a line or in one spot are refused as ConvexHullError::Degenerate, points on or close to a plane as ConvexHullError::Coplanar: a flat hull has no volume, so Jolt would give a dynamic body made of it zero mass and a meaningless inertia, and Jolt’s single-precision hull builder cannot build very thin needles and slabs reliably. “Close” grows with the cloud’s length and its distance from the shape origin; docs/limits.md#convex-hulls gives the rules. Use a mesh, a capsule or a thin box instead. Whatever else Jolt’s hull builder refuses comes back as ShapeError::Rejected.

With the asserts feature Jolt’s hull builder can abort the process on some clouds with many nearly coplanar faces: densely sampled faces a few coplanar distances off their planes, dense flat cones and domes. Without it Jolt refuses most of them (ShapeError::Rejected) and builds a hull from the rest; see docs/limits.md#clouds-the-hull-builder-asserts-on.

Jolt keeps at most 256 vertices of the hull and drops the points inside it. It shrinks the hull by the convex radius and inflates it again, reducing the radius where the hull is too small for it; contacts and shape casts use at most 0.05 m of it, ray casts see the hull without it. The hull’s centre of mass becomes its shape-space reference for bodies, and its local bounds, relative to that centre, must lie within limits::MAX_SHAPE_EXTENT. See docs/limits.md#convex-hulls.

Source

pub fn new_convex_hull_with_material( points: &[Vec3], convex_radius: f32, material: &PhysicsMaterial, ) -> Result<Self, ShapeError>

new_convex_hull_with_convex_radius made of material; the same rules apply.

Source§

impl Shape

Source

pub fn new_mesh( vertices: &[Vec3], triangles: &[[u32; 3]], ) -> Result<(Self, DroppedTriangles), ShapeError>

A triangle mesh of vertices (shape space, metres) and triangles (three indices into vertices each) with MeshSettings::default, and the triangles it dropped; see new_mesh_with_settings.

Source

pub fn new_mesh_with_settings( vertices: &[Vec3], triangles: &[[u32; 3]], settings: &MeshSettings<'_>, ) -> Result<(Self, DroppedTriangles), ShapeError>

A triangle mesh of vertices (shape space, metres) and triangles (three indices into vertices each), and the triangles it dropped.

A triangle’s front face is the side from which its vertices run counter-clockwise. Triangles too small or too thin for Jolt to collide with reliably are dropped and reported in DroppedTriangles: twice a triangle’s area must be above 1e-6 m² plus twice the largest change Jolt’s 21-bit vertex quantization and f32 rounding can make to it. That margin follows the triangle’s own shape and distance from the shape origin, the quantization step of the mesh’s bounds on each axis and the size of the convex shapes it collides with (MeshSettings::max_convex_extent); with the defaults a strip 1 m long near the origin is kept from about 0.15 mm wide along the axes and from 0.26 mm in any orientation (docs/limits.md#triangle-meshes). Jolt itself keeps one copy of duplicate triangles and reorders the rest, so sub-shape ids do not follow the input order. Closest-hit rays hit back faces too.

let vertices = [Vec3::ZERO, Vec3::new(0.0, 0.0, 1.0), Vec3::new(1.0, 0.0, 0.0)];
// The second triangle repeats a vertex.
let (mesh, dropped) = Shape::new_mesh(&vertices, &[[0, 1, 2], [0, 0, 1]])?;
assert_eq!(dropped.indices(), [1]);

Meshes have no volume. They suit static bodies, and kinematic bodies with an explicit BodySettings::mass; PhysicsWorld::create_body refuses them for dynamic bodies.

§Errors

Building cost grows with the triangle count; see docs/benchmarks.md and docs/limits.md#triangle-meshes.

Source§

impl Shape

Source

pub fn new_plane( normal: Vec3, constant: f32, half_extent: f32, ) -> Result<Self, ShapeError>

A plane normal · p + constant = 0 in the shape’s frame (Jolt PlaneShape); everything on the side away from the normal, normal · p + constant < 0, is solid.

The plane is only infinite in name: it ends at a square of 2 * half_extent metres around the point -constant * normal, and its bounds reach half_extent metres behind it. Jolt does not collide anything outside these bounds and gives inconsistent contacts at their edge, so half_extent should leave room around where bodies will be (and stay small, for the broad phase).

Only static bodies, and compounds or decorators on static bodies, may use a plane: Jolt marks it MustBeStatic, it has no volume or mass, and Jolt cannot collide it with meshes, heightfields or other planes. It collides with convex shapes (also as compound or decorated children), soft bodies and characters, and ray and shape casts hit it. It cannot be scaled (new_scaled refuses it), and query, character and ragdoll shapes refuse it. A ray that starts behind the plane hits it at fraction 0 (solid, <= 0), while a point query reports only points strictly behind it (< 0).

normal must be a finite unit vector, constant finite and at most limits::MAX_SHAPE_EXTENT in absolute value, half_extent positive and at most limits::MAX_SHAPE_EXTENT, and the bounds within limits::MAX_SHAPE_EXTENT on every axis; otherwise ShapeError::InvalidValue or, for the normal, ShapeError::InvalidValue. For a normal along an axis the bounds reach max(|constant|, |constant + half_extent|) along it: with normal +Y a half extent of 2000 m fits for constant = -1 (plane at y = 1) but not for constant = 1.

let mut world = PhysicsWorld::new(WorldSettings::default())?;
// Ground at y = 0, solid below, 500 m in every direction.
let ground = Shape::new_plane(Vec3::new(0.0, 1.0, 0.0), 0.0, 500.0)?;
world.create_body(&ground, &BodySettings::new_static())?;
Source

pub fn new_plane_with_material( normal: Vec3, constant: f32, half_extent: f32, material: &PhysicsMaterial, ) -> Result<Self, ShapeError>

new_plane made of material; the same rules apply.

Source§

impl Shape

Source

pub fn new_scaled(shape: &Shape, scale: Vec3) -> Result<Self, ShapeError>

shape scaled by scale along its local axes, a Jolt ScaledShape. The new shape holds its own reference to shape, which may be dropped afterwards.

Each component must be finite (ShapeError::InvalidValue) and the scale valid for the shape under Jolt’s rules (ShapeError::InvalidValue): every component at least 1e-6 in absolute value; uniform for spheres, capsules and tapered capsules; uniform in X and Z for cylinders and tapered cylinders; for a compound, uniform unless every rotated child is turned so the scale maps onto its own axes. Negative components mirror the shape; a mirrored mesh’s front faces follow the mirrored winding.

Meshes and heightfields inside the shape must stay collidable: each stored triangle, scaled, must pass the rule Shape::new_mesh applies, for the MeshSettings::max_convex_extent the mesh was built with (the default for heightfields) and without its quantization term (the stored triangles are quantized already). Shrinking a mesh far below the size it was built at, or flattening it, is refused with ShapeError::ThinTriangles, which names the scale and the extent (docs/limits.md#scaled-shapes). The check reads every stored triangle back from Jolt, so its cost grows with the triangle count.

The scaled shape’s local bounds must lie within limits::MAX_SHAPE_EXTENT on every axis, and so must its centre of mass, which moves with the scale (ShapeError::InvalidValue). Mass and inertia scale with the shape; a body made of it goes through the usual mass and inertia checks of PhysicsWorld::create_body. A shape that only static bodies may use stays static-only, and a scaled mesh stays usable by kinematic bodies. Jolt scales the convex radius by the smallest absolute component, and contacts use at most 0.05 m of it. compound_sub_shape does not look through the decorator: it returns None for a scaled compound.

Source§

impl Shape

Source

pub fn new_tapered_capsule( half_height: f32, top_radius: f32, bottom_radius: f32, ) -> Result<Self, ShapeError>

A capsule along the local Y axis whose end spheres differ: a cone section 2 * half_height metres high between the centres of a sphere of top_radius at y = half_height and one of bottom_radius at y = -half_height, a Jolt TaperedCapsuleShape. Its centre of mass is halfway between the outer ends of the two spheres (Jolt’s approximation).

All values must be finite and positive, half_height + max(top_radius, bottom_radius) at most limits::MAX_SHAPE_EXTENT, and the radii may differ by at most 2 * half_height * (1 - 2^-21); otherwise one sphere contains the other and the shape is a sphere, which new_sphere builds (ShapeError::InvalidValue; docs/limits.md#tapered-shapes). Only a uniform new_scaled applies.

Source

pub fn new_tapered_cylinder( half_height: f32, top_radius: f32, bottom_radius: f32, ) -> Result<Self, ShapeError>

A tapered cylinder with Jolt’s default convex radius, 0.05 m; otherwise as new_tapered_cylinder_with_convex_radius.

Source

pub fn new_tapered_cylinder_with_convex_radius( half_height: f32, top_radius: f32, bottom_radius: f32, convex_radius: f32, ) -> Result<Self, ShapeError>

A cylinder along the local Y axis whose ends differ: 2 * half_height metres high with a disc of top_radius at the top and one of bottom_radius at the bottom, a Jolt TaperedCylinderShape. A radius of 0 makes a cone. The centre of mass lies on the axis at the centroid of the volume.

The half height must be finite and positive, the radii finite, not negative and different (equal radii make a cylinder: use new_cylinder_with_convex_radius), the larger radius at least 2^-63 m, and all of them at most limits::MAX_SHAPE_EXTENT (ShapeError::InvalidValue; docs/limits.md#tapered-shapes). The convex radius must be finite and not negative; Jolt clamps it to the smaller radius, and contacts use at most 0.05 m of it. Only a new_scaled that is uniform in X and Z applies.

Source§

impl Shape

Source

pub fn new_box(half_extent: Vec3) -> Result<Self, ShapeError>

A box with the given half extents in metres (each finite, positive and at most limits::MAX_SHAPE_EXTENT) and Jolt’s default convex radius of 0.05 m; see new_box_with_convex_radius.

Source

pub fn new_box_with_convex_radius( half_extent: Vec3, convex_radius: f32, ) -> Result<Self, ShapeError>

A box with the given half extents in metres (each finite, positive and at most limits::MAX_SHAPE_EXTENT) and convex radius in metres (finite and not negative).

Jolt shrinks the box by the convex radius and inflates it again, so the faces stay where they are while edges and corners are rounded for contacts and shape casts. Jolt clamps the radius to the smallest half extent (BoxShape.h), and contacts and shape casts use at most 0.05 m of it (ScaleHelpers::ScaleConvexRadius caps it at Jolt’s default), so a larger radius collides like 0.05. A radius of 0 gives sharp edges; collision detection is then somewhat slower, because Jolt falls back to EPA more often. Ray casts always see the sharp box, whatever the radius (BoxShape::CastRay tests the half extents only).

Source

pub fn new_sphere(radius: f32) -> Result<Self, ShapeError>

A sphere with the given radius in metres (finite, positive and at most limits::MAX_SHAPE_EXTENT).

Source

pub fn new_cylinder(half_height: f32, radius: f32) -> Result<Self, ShapeError>

A cylinder along the local Y axis, centred on the origin, 2 * half_height metres high, with Jolt’s default convex radius of 0.05 m; see new_cylinder_with_convex_radius.

Source

pub fn new_cylinder_with_convex_radius( half_height: f32, radius: f32, convex_radius: f32, ) -> Result<Self, ShapeError>

A cylinder along the local Y axis, centred on the origin, 2 * half_height metres high.

Half height and radius must be finite, positive and at most limits::MAX_SHAPE_EXTENT, the convex radius finite and not negative. Like a box’s, the convex radius rounds the edges for contacts and shape casts; Jolt clamps it to min(half_height, radius) (CylinderShape.cpp) and uses at most 0.05 m of it there (ScaleHelpers::ScaleConvexRadius).

Source

pub fn new_capsule( half_height_of_cylinder: f32, radius: f32, ) -> Result<Self, ShapeError>

A capsule along the local Y axis, centred on the origin: a cylinder 2 * half_height_of_cylinder metres high with a hemisphere of radius at each end, so 2 * (half_height_of_cylinder + radius) metres high in total. Both values must be finite and positive, and half_height_of_cylinder + radius at most limits::MAX_SHAPE_EXTENT.

Source

pub fn new_height_field( sample_count: u32, samples: &[f32], settings: &HeightFieldSettings<'_>, ) -> Result<Self, ShapeError>

A heightfield of sample_count x sample_count height samples, in metres before scaling.

§Layout

The surface passes through offset + scale * (x, samples[y * n + x], y) for x, y in 0..n, n = sample_count. y runs along +Z, so row y of samples holds the heights at z = offset.z + y * scale.z (Jolt’s row-major order). A column-major source heights[x * n + z] must be transposed first:

samples[z * n + x] = heights[x * n + z]

Each cell is split into two triangles along the diagonal from sample (x, y) to sample (x + 1, y + 1). A sample of f32::MAX is a hole: the cells touching it have no collision. Every other sample must be finite.

§Block size

Jolt rounds n up to a multiple of the block size and fills the extra rows and columns with holes, so the cells touching them have no collision. With the default block size 2, n = 33 is stored as 34 x 34 and the surface covers exactly the 32 x 32 cells given. n / block_size, rounded up, must be at least 2.

§Precision

Jolt first quantises the heights to 16 bits over the height range of the whole field, then to HeightFieldSettings::bits_per_sample bits within the height range of each block. height_field_position reads back the stored heights.

§Static only

PhysicsWorld::create_body refuses heightfields for dynamic and kinematic bodies.

§Extent

The shape’s local bounds must lie within limits::MAX_SHAPE_EXTENT on every axis; otherwise ShapeError::InvalidValue is returned. A field of holes only has empty bounds and passes.

Source

pub fn height_field_position(&self, x: u32, y: u32) -> Option<Vec3>

The stored surface point of heightfield sample (x, y) in shape-local space, after Jolt’s quantisation. None when the shape is not a heightfield, when (x, y) lies outside the stored (padded) grid, or when the sample is a hole, including the padding Jolt adds.

Source

pub fn new_compound(children: &[CompoundChild<'_>]) -> Result<Self, ShapeError>

A compound of children, each with its own pose and user data.

The children may be dropped afterwards: the compound holds its own references. Child order is part of the shape and so of a deterministic state. Jolt moves the compound’s centre of mass to the children’s mass-weighted centre, so body positions of compounds read back through arithmetic.

Two or more children make a Jolt StaticCompoundShape. A single child makes a MutableCompoundShape, because Jolt’s static compound replaces a lone child by the child itself (or a RotatedTranslatedShape) and drops its user data (StaticCompoundShape.cpp); oxijolt never changes it after construction.

Every child position must be finite with each component at most limits::MAX_SHAPE_EXTENT in absolute value (ShapeError::InvalidValue), and the compound’s local bounds must lie within limits::MAX_SHAPE_EXTENT on every axis (ShapeError::InvalidValue). A hierarchy whose sub-shape ids Jolt cannot form gives ShapeError::Rejected when it needs more than 32 bits and ShapeError::InvalidValue when a one-child compound would start at bit 32. A compound of more than limits::MAX_EXPANDED_SUB_SHAPES shapes, counting a child shared by several parents at every use, gives ShapeError::TooManySubShapes.

Source

pub fn new_offset_center_of_mass( shape: &Shape, offset: Vec3, ) -> Result<Self, ShapeError>

shape with its centre of mass moved by offset (shape space, metres, each component at most limits::MAX_SHAPE_EXTENT in absolute value), a Jolt OffsetCenterOfMassShape. The new shape’s local bounds, which are relative to the new centre of mass, must lie within limits::MAX_SHAPE_EXTENT on every axis.

Only the centre of mass moves: the body origin and the collision surface stay where shape puts them, and mass and inertia are computed about the new centre. A vehicle chassis gets a low centre of mass this way. The new shape holds its own reference to shape, which may be dropped afterwards. A decorated shape that only static bodies may use (a heightfield, mesh or plane) stays static-only. compound_sub_shape does not look through the decorator: it returns None for an offset compound.

Source

pub fn compound_sub_shape(&self, id: SubShapeId) -> Option<CompoundSubShape>

The child of this compound that id leads to.

None for shapes that are not compounds. Only the root level is decoded, so id may be a hit’s full path or a partial path below this shape. An id that came from another shape gives None or an arbitrary valid child: the bits cannot prove where they came from.

Trait Implementations§

Source§

impl Debug for Shape

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Send for Shape

Source§

impl Sync for Shape

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.