RealityBox3D
Erin Catto's Box3D inside RealityKit.
TL;DR: RealityBox3D runs Box3D inside RealityKit using components named after RealityKit's own physics conventions: B3PhysicsBodyComponent where you would write PhysicsBodyComponent, B3CollisionComponent, B3PhysicsRevoluteJoint, entity.b3.applyLinearImpulse, and so on...
Erin Catto announced Box3D some months ago as the 3D fork of Box2D. Box3D brings some interesting features that are not currently exposed in RealityKit, such as substeps, continuous collision for bullets, rolling resistance, and ways to know where a throw lands before it does (precalculation of resolution). In this wrap, Box3D is vendored unmodified as C, and the Swift layer on top makes it feel familiar to RealityKit's users.
Same shape as RealityKit
RealityBox3D.register() // once, before creating entities
let root = Entity()
root.components.set(B3PhysicsSimulationComponent())
let die = try await Entity(named: "D20")
die.components.set(B3PhysicsBodyComponent(density: 1200, material: .generate(dynamicFriction: 0.5, restitution: 0.35)))
die.components.set(B3CollisionComponent(shapes: [.generateConvex(from: die)]))
root.addChild(die)
die.b3.applyLinearImpulse([0, 0.02, 0], relativeTo: nil)
PhysicsBodyMode, CollisionFilter, CollisionGroup and GeometricPin stay RealityKit's own types, and joints follow RealityKit's axes, so a revolute joint turns about the pins' x-axis as it does with PhysicsRevoluteJoint. Next to the mirrored parts are the things Box3D has and RealityKit doesn't: wheel and motor joints, joints that break (B3JointEvents.Broke), conveyor belts (tangentVelocity on a material), wind, one-way platforms, and explosions 💥
To check that the API holds, 32 of Box3D's sample scenes were rebuilt in RealityKit, using similar sizes, densities, materials, and cameras.
A reel of Box3D's sample scenes rendered in RealityKit, each with a slowly orbiting camera: a spiral of 5,431 colored dominoes falls ring by ring from its center outwards; a fast sphere fired at a stack of colored boxes against a wall knocks the top boxes off; a tall column of 5,121 small convex shapes collapses into a pile; a heavy ball swings on a long chain of capsule links; five colored boxes ride a conveyor belt along a gray platform; and a chain of plates flaps in a gusting wind. A panel in the top left names each scene with its bodies, awake bodies, and milliseconds per step
Character control
B3CharacterControllerComponent and entity.b3.moveCharacter(by:deltaTime:relativeTo:) mirror RealityKit's character controller, running on Box3D's geometric mover. The Geometric Mover demo scene was really cool to reimplement (with WASD, jump, and sprint). A violet capsule is a hard obstacle that stops the character, the green one is soft (character can lean into it) using B3CharacterObstacleComponent, with a push limit for each.
Stairs were the surprise: Box3D's mover has no step-up. Upstream's sample climbs them with a sprung ray under the capsule.
A blue capsule character in a gray test level. Seen from the side, it walks into a violet capsule and stops against it, then goes around and walks into a green capsule, sinking partway into it. A third-person camera then follows it past a green door, around a staircase, up it step by step and off the top in a jump. A panel in the top left says what the character is doing and its speed in meters per second
Knowing the roll before it lands
Box3D is deterministic across platforms. world.forecast() copies the world from the snapshot that opens a Box3D recording, including contacts and warm starts, and steps the copy exactly as the world will step (the tests check the poses bit for bit). To try this, we ported Apple's dice sample, and show the score while the dice still tumble, and also we added a button to trigger "Max score toss" which shows how to turn each die's model by one of its symmetries so its highest face lands on top.
Apple's seven white dice on a green round table: a D4, a D6, two D8s, a D10, a D12 and a D20. They are lifted together in a ring, carried to the right and thrown. While they tumble, the panel in the top left shows the forecast "D4 4 · D6 6 · D8 8 · D8 (1–35) 35 · D10 10 · D12 12 · D20 20 = 95"; once they rest it reads "rolled", with the same values and "as forecast"
The cradle
Apple's Newton's cradle sample port gave some fight. Box3D's solver, like RealityKit's, solves all the contacts of a step together, so when two balls hit the row, their momentum spreads along it, and the cradle shuffles instead of clicking.
Apple's Newton's cradle, five chrome balls hanging on thin strings from a pale green bar, on a light gray background. Twice, the two balls on the left are lifted and let go. The first time, labeled "Box3D's solver alone", the impact spreads along the row and the balls drift apart unevenly. The second time, labeled "sequentialImpacts on", the two balls on the right swing out and the three in the middle stay still
sequentialImpacts on the simulation component resolves sphere impacts pair by pair before each step, as in Guendelman, Bridson and Fedkiw: two balls in, two balls out, and the middle ones stay still.
Get it
.package(url: "https://github.com/elkraneo/RealityBox3D.git", from: "0.2.1")
swift run -c release Box3DDemos opens the 32 scenes and Box3DRealityKitSamples opens the current ports of Apple's samples. The apps run on macOS; the library builds for iOS and visionOS but hasn't been tested thoroughly yet.
Its sibling, RealitySDFPhysics, is the same spirit but for Signed Distance Fields, with the same conventions (entity.b3 there is entity.sdf), and the two can run side by side in one scene (although they cannot interact).
Erin Catto. Box3D, a 3D physics engine for games. MIT License. github.com/erincatto/box3d
Eran Guendelman, Robert Bridson and Ronald Fedkiw. 2003. Nonconvex rigid bodies with stacking. ACM Transactions on Graphics 22(3). doi:10.1145/882262.882358
