Repository navigation
Releases: recursive-ai-oss/onephysics-csharp
Release list
v0.4.0-preview.1
-
Soft bodies complete the 3D feature set for this preview line; the 3D engine stays
[Experimental("OPHYS0002")]. Every 3D scene, soft bodies included, is within its stability limits on x64 and arm64 with identical metrics, and results are bit-identical across x64, arm64, WebAssembly, vector widths and thread counts. Not every speed row meets the bar yet (within 1.15x of the fastest stable managed engine, BepuPhysics v2 or Jitter2, per scene and thread count). Measured on a Ryzen 9 7950X3D (ours / reference median step time, t1 / t8 on one core complex / t32):scene t1 t8 t32 large-pile-3d 1.05 1.08 1.25 ragdoll-pile-3d 2.19 1.12 1.70 sleep-wake-3d 2.51 1.51 1.40 queries-3d (settle phase) 1.27 1.26 1.19 cloth-drape-3d (soft body, against Jitter2) 0.93 3.9 2.5 heightfield-terrain-3d 1.13 0.89 0.64 mesh-terrain-3d 1.08 0.89 0.85 joint-grid-3d 0.10 0.16 0.09 On an Apple M2 (t1 / t8) every 3D row meets the bar except ragdoll-pile-3d t1 (1.27) and sleep-wake-3d t1 (1.64), and cloth-drape-3d is 0.84 / 1.87. joint-chains-3d and pyramid-3d-30 have no stable managed reference, and the other soft-body scenes (soft-pile-3d, soft-rigid-3d, soft-mesh-3d, soft-skinned-3d) have none either; they are judged on their stability limits, which they meet. Every 2D scene is within the bar at every thread count.
-
3D soft bodies solve about 2.5 times faster on a cloth (indicative, one thread), and large bodies use several threads. Moved results (soft bodies only; rigid worlds and 2D are unchanged). The step works on float32 positions relative to each body, the constraints are coloured once at creation (edges, bends and tetrahedra solved colour by colour, in
Vector128lanes where four constraints of a colour are available, with the same bits as the scalar loop), bodies of 1024 particles or more are split across threads by colour, contact detection and the particle pair search run in tasks that are joined in particle order, and particle pairs are searched once per step instead of once per substep. The order in which constraints are solved changes, so soft-body states, snapshot bytes and hashes differ from the previous preview; the snapshot layout is unchanged. Details indocs/spec/softbody.md(Solver). -
3D soft bodies follow a skeleton (skinning).
SoftSkin.Createbuilds per-particle joint weights (four influences, linear blend skinning), a maximum distance and an optional back-stop sphere from a skeleton and the mesh; set it asSoftBodyDef.Skin.SoftBody.SetSkeletonPose(ragdoll-style local rotations) orSetSkeletonTransforms(world joint poses) drives it before each step, withteleportto place or reset;GetSkinnedPosition,CopySkinnedPositions,HasSkin,SlotandPhysicsWorld3D.GetSoftBody(slot)(to find the bodies again afterRestore) are new. A particle with maximum distance 0 is kinematic and equals its linearly blended position bit for bit; the others are held within their distance (and out of the back-stop) after every step, and win against contacts. Snapshot section version 2 is written only while a skinned body exists, so other worlds keep their bytes and hashes; version 1 sections still load.DebugSoftBody.FirstTargetPointcarries the targets. New scenesoft-skinned-3d(a sleeve on a swinging arm) withskinMaxDistance,skinBackstopandskinCuffErrormetrics. Seedocs/spec/softbody.md. -
3D: the bounding-box stage takes about half the time, with identical results. The world box of a sphere, box, capsule or sharp hull is computed straight from the body arrays with only the rotation-matrix entries the shape needs, and a hull's six extremes come from one pass with the three axes as lanes of one 128-bit vector (the same operations in the same order as the scalar code, so every platform and vector width gives the same bits). Profile build, one thread, indicative: large-pile-3d 0.24 to 0.11 ms, ragdoll-pile-3d 0.08 to 0.04, sleep-wake-3d 0.07 to 0.035. No world hash moves. See
docs/spec/world3d.md, round 35. -
3D soft bodies (core).
PhysicsWorld3D.AddCloth,AddSoftBox,AddSoftBall,AddSoftBody(SoftBodyMesh, ...)andAddSoftBody(SoftBodyDef)make cloth, rubber and jelly from a triangle or tetrahedral mesh: extended position-based dynamics with edge, isometric-bend, tetrahedron-volume and pressure constraints, particles that collide with every shape, with each other and with friction, two-way coupling with dynamic rigid bodies, pins, sleeping, debug geometry (DebugContent.SoftBodies),SoftSubstepsandSoftIterationsoptions. TheSoftBodyhandle reads particles without allocating. Soft bodies are stored apart from rigid bodies and solved serially after the rigid step, in a new optional snapshot section (0x534F4654) that is written, hashed and counted only once a soft body exists: worlds without soft bodies keep every byte and hash, and snapshot format version 1 is unchanged. New scenescloth-drape-3d,soft-pile-3d,soft-rigid-3d,soft-mesh-3dwith soft-body metrics and a Jitter2 driver; fuzz targetsworld3d-softbodiesandworld3d-softbody-snapshot.DebugContent.Allis now 1023; it is a constant, so code compiled against an earlier preview keepsAll= 511 and does not draw soft bodies until it is rebuilt.
v0.3.0-preview.1
-
Characters, vehicles, ragdolls and buoyancy are complete for this preview line. The rigid and virtual 3D characters, wheeled, tracked and motorcycle vehicles, ragdolls with powered pose matching, and buoyancy and drag in fluid volumes are within their stability limits on every scene, with identical results on x64 and arm64:
characters-3d(every stair climbed, character jitter under 1e-6 m/s, no slope violations),vehicles-3d(no rollovers, no wheel through the ground, suspension jitter 0.002),tracked-3d,motorcycle-3d(none fallen, lean error 0.006),ragdoll-powered-3d(95th-percentile pose error 0.058 rad, none fallen) andbuoyancy-3d(equilibrium depth error 0.0026). No managed .NET engine covers these scenes, so they are judged on their stability limits. They stay under[Experimental("OPHYS0002")]with the rest of the 3D engine until every 3D speed row meets the bar. -
GetSnapshotSizeBound()onPhysicsWorld2DandPhysicsWorld3Dreturns an upper bound onGetSnapshotSize()that holds however the world steps while no storage grows, so a host can size rollback buffers once. Contacts and joints, shape pairs, islands, broadphase nodes and events are counted at the capacity that holds them (ContactCapacity,PairCapacity); what the host creates (bodies, shapes, joints, characters, vehicles, ragdolls, fluids) is counted as it is now. Additive; the snapshot format and every world hash are unchanged. -
3D box-against-box edge axes are tested without normalizing each axis (about 9% off the box–box routine on pairs from a sleep-wake pile). Separations can differ from the previous preview in the last bit in some poses, so 3D world hashes and snapshot bytes move.
-
3D: mild joints run on the contact-only schedule. An island whose joints are all mild (no kinematic end, motor, spring or pose drive; mass ratio at most 50; no more joints than bodies) now takes the 2-substep contact-only schedule with its contacts, instead of the world's 4. ragdoll-pile-3d solve stage 0.67× the time (step about 0.8×, one thread, indicative); stiff joint scenes are unchanged. See
docs/spec/world3d.md, round 33. Step time on a locked machine: ragdoll-pile-3d 0.80× at one thread, 0.85× at eight, 0.75× at 32; large-pile-3d and the joint scenes within noise. -
3D steps scale better with threads, with identical results. The sleep timers run in parallel blocks as in 2D, colouring blocks are 512 constraints instead of 4096, and threads waiting at a stage boundary pause for a few instructions a round and give the core up only after a long wait, instead of backing off and yielding after ten rounds. On a locked machine, steps at eight and 32 threads take 0.94–0.99× the time on piles, ragdolls, sleep-wake and joint grids, in 2D and 3D. No world hash moves.
v0.2.0-preview.1
-
The snapshot format is frozen at version 1. Its layout (header, options, bodies, shapes, constraints, islands and registered sections,
docs/spec/architecture.md) no longer changes within version 1. A later change is a new format version. Results may still move between previews. -
Raw tier (
[Experimental("OPHYS0001")]):IBox<TSelf>gainsTranslated(ReadOnlySpan<double>), which the exact origin shift uses. A type that implementsIBox<TSelf>outside the library must add it. -
The 3D engine stays
[Experimental("OPHYS0002")]. It meets the speed bar on most scenes but not all yet: within 1.15x of the fastest stable managed engine (BepuPhysics v2, Jitter2), per scene and thread count. Measured on a Ryzen 9 7950X3D (ours / reference median step time, t1 / t8 on one core complex / t32):scene t1 t8 t32 large-pile-3d 1.10 1.23 1.47 ragdoll-pile-3d 2.75 1.42 2.17 sleep-wake-3d 2.75 1.60 1.41 queries-3d (settle phase) 1.31 1.42 1.38 heightfield-terrain-3d 1.17 0.95 0.68 mesh-terrain-3d 1.10 0.92 0.68 joint-grid-3d 0.10 0.16 0.06 joint-chains-3d and pyramid-3d-30 have no stable managed reference and are judged on their stability limits, which they meet. Every 3D scene is within its stability limits, and every 2D scene is within the bar at every thread count. These rows predate the narrowphase entry below, which takes 3-6% off the ragdoll, box-pile and sleep-wake rows.
-
Warmupprimes more of the process. Its trial world now drops a pile that lands, settles and falls asleep (150 steps) and runs box and sphere casts and box and capsule overlaps, so those paths are loaded before a host's first step. Worlds, hashes and snapshots are unchanged.
Moved results: 3D worlds move (the broadphase keeps tighter boxes, so pairs start and end at different steps), and 2D and 3D worlds move again because positions now advance on a 2^-32 grid (see the origin shift entry).
-
3D narrowphase is cheaper in ragdoll and box piles, with identical results (ragdoll-pile-3d about 5%, sleep-wake-3d about 3%, large-pile-3d about 4% per step, one thread, indicative): a capsule against a box skips box edges that cannot be nearest, box edge axes are rejected before they are normalized, separated boxes nearest on an edge pair skip GJK, and pairs that need a narrowphase run in their own method. No world hash moves.
-
3D ray casts and sphere casts are faster (ray 0.59x, sphere cast 0.79x,
OverlapSphereAll0.84x the time on a 3000-body pile, one thread, indicative). Spheres, capsules, boxes and hulls are tested in closed form, and the GJK route only runs for starts inside, grazing hits, edge regions and other shape kinds. Which shape is hit and the hit counts do not change; the last bits ofFraction,Distance,PointandNormalof ray and sphere-cast hits do (within the old route's tolerance), so worlds that cast (vehicle wheel rays) move. -
3D: a substep schedule per island. Islands without a joint (and not touching a kinematic body or holding a vehicle) now run 2 substeps of 1 solve and 2 relax passes (
WorldOptions3D.ContactSubsteps,ContactVelocityIterations,ContactRelaxIterations;ContactSubsteps = 0restores the world-wide schedule); islands with joints keepSubsteps. Piles and terrain step about 1.15-1.35x faster and worlds with those islands move; adding a joint changes only the island that receives it. The options are saved in snapshots and covered by the world hash. -
3D broadphase defaults:
BroadphaseMarginis 0.03 (was 0.1), boxes stretch over four steps of motion, and the tree is refitted between rebuilds. About half as many pairs are kept alive in piles; sleep-wake scenes step about 1.2× faster and large piles at eight threads about 1.15×. Stability is unchanged on every 3D scene. -
A fast body still inside the supported range no longer fails the step with an argument error when its stretched box would cross the range limit; the stretch stops at the limit.
-
ShiftOriginis exact. A shifted world now steps through exactly the states of the unshifted world, translated (positions differ by the offset; velocities, rotations and contact data are bit-identical), instead of drifting from it. The shift moves the stored broadphase boxes in place instead of rebuilding them. To make that true, a body's position increment each step is rounded to a multiple of 2^-32 (2.3e-10 m; a body slower than about 1.4e-8 m/s at 60 Hz does not move), shape bounds are rounded outward to the same grid, and the broadphase margin is rounded to it. Positions handed in as 31.32 fixed-point values are already on the grid. -
3D box-against-box face contacts are built without loading polyhedra. The reference face is now the one the separating axis test chose (no second choice by area), so manifolds and the worlds that contain boxes move. The box-box manifold costs about 0.78× as much per fresh manifold in a sleep-wake pile (845 against 1090 ns).
v0.1.0-preview.6
- Fix: 0.1.0-preview.5 could refuse a snapshot of a valid 3D world. Its
Restorerejected saved contact impulses above a fixed plausibility limit, but the solver can legitimately produce larger ones (a very heavy body on a light one, or a tight pile, gave impulses in the trillions). A world that had stepped normally could therefore write a snapshot that the same library then refused withSnapshotFormatException, which breaks a host's rollback. This release removes that check, and the wheel and engine speed limits added with it: a snapshot the library wrote always restores, and saved impulses are checked only for being finite. A damaged snapshot that carries an enormous impulse restores, and the next step reports the flung body withBodyOutOfBoundsException(the world unchanged), as before. No step result moves: world hashes and snapshot bytes are the same as in preview.5 for the same inputs. - 3D
Restorerefuses a dynamic body whose saved extents do not match its shapes (SnapshotFormatException, world unchanged). A snapshot the library wrote always matches. - 3D steps are faster, with identical results. Pairs whose contacts are unchanged skip the serial write-back; the tree update tests boxes in parallel; height-field collision visits only the cells a box covers; a convex shape stays loaded across the triangles of a mesh; overlap counts no longer collect hits; solver batches precompute their contents once per step. Height-field worlds step about 1.1–1.2× faster and large piles at eight threads about 1.1×. No result moves.
- Vehicles: a step can no longer write state its own snapshot reader refuses. Fuzzing found a differential ratio of about 3e37 (finite, so accepted) that overflowed the drivetrain into NaN in one step. Vehicle definitions now bound their ratios (at most 1000), inertias (0.001 to 1e6), engine torque, clutch strength, tyre factors and low speed, and
AddVehicleandRestorerefuse values outside the bounds with the usual argument and snapshot errors. The step also keeps engine and wheel speeds finite, andRestorechecks them against what a step can write. Valid worlds are unchanged. - Tests now check, after every step of every fuzz scenario (2D and 3D), that the world's snapshot restores into a fresh world and re-serializes to the same bytes.
v0.1.0-preview.5
First public release. The 2D engine and the shared core are the supported surface; the 3D engine ships as a preview marked [Experimental("OPHYS0002")]. A user guide is in docs/guide/.
Moved results: 2D worlds are bit-identical to preview.4 for the same inputs. 3D worlds move: an exact capsule-against-box closest pair replaces the general path when the shapes are apart, and the joint solver computes each joint's block inverse once per substep. Convex hulls built from inputs that were near-coplanar or noisy can differ (see below). Snapshot layout is unchanged.
- Allocation-free from the first step, also under load and with several threads.
Warmup()now prepares every pool thread (including threads an earlier world started) for the narrowphase and batch queries, runs the pool's parallel paths of a throwaway world whatever their size, and stocks the island body-list pool for the world's body count. Before, a pile that kept merging and splitting islands allocated a few hundred bytes at about one step in a few hundred, and the first step of a multi-threaded 3D world allocated on the calling thread.ComputeHash()is documented as streaming (it always was: no snapshot copy is made). - Breaking:
CharacterDef.SlopeLimitandCharacter.SlopeLimitareSlopeLimitRadians(2D and 3D); the unit is in the name. The value and its default are unchanged. world.BodyCount,world.Bodies,world.CharacterCount,world.Characters(2D and 3D, identical): count andforeachover the live bodies and characters in slot order with a struct enumerator that allocates nothing.- The 3D engine is
[Experimental("OPHYS0002")]: every public type inRecursiveAI.OnePhysics.Physics3D. Using it raises the diagnostic until you suppress it (#pragma warning disable OPHYS0002or<NoWarn>); its API and its results may change between previews. The 2D engine and the shared types are not marked. - The package now carries the README.
- Convex hulls contain their input points.
ConvexHull3D.Buildnow decides face visibility with exact predicates, rescans every input point once the hull is built, and checks each merged face against every point, undoing a merge that would leave a point outside. Every input point is inside every face within the documented tolerance, or the build is refused withHullBuildException; before, some near-coplanar inputs gave a hull that left a point outside by up to a few centimetres. Building is about 20% slower on hostile inputs. - 3D
Restorerefuses damaged contact and vehicle state, and a step reports a runaway sleeping body. A snapshot holding a saved contact impulse no solver step could have produced (it would launch the bodies out of the world in one step), or a vehicle wheel with an absurd sprung mass or wheel or engine speed, is refused withSnapshotFormatExceptionnaming the value, and the world is unchanged. A sleeping body far from the origin is now checked at the start of every step (BodyOutOfBoundsException, world unchanged), and a vehicle wheel whose top lies just outside the supported range finds no ground instead of failing the step. Valid worlds are bit-identical. - 3D, faster at 8 threads: solve stages split into one task per constraint batch and the worker count follows the average stage width (terrain scenes about 1.5–1.8× faster at 8 threads); results do not depend on the thread count, as before.
- 3D integration runs lane-wide with a bit-identical scalar twin.
- Package metadata, symbols (
.snupkg) and Source Link for the public repository.