Skip to content

Releases: recursive-ai-oss/onephysics-csharp

v0.4.0-preview.1

v0.4.0-preview.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 30 Oct 18:53
  • 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 Vector128 lanes 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 in docs/spec/softbody.md (Solver).

  • 3D soft bodies follow a skeleton (skinning). SoftSkin.Create builds 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 as SoftBodyDef.Skin. SoftBody.SetSkeletonPose (ragdoll-style local rotations) or SetSkeletonTransforms (world joint poses) drives it before each step, with teleport to place or reset; GetSkinnedPosition, CopySkinnedPositions, HasSkin, Slot and PhysicsWorld3D.GetSoftBody(slot) (to find the bodies again after Restore) 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.FirstTargetPoint carries the targets. New scene soft-skinned-3d (a sleeve on a swinging arm) with skinMaxDistance, skinBackstop and skinCuffError metrics. See docs/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, ...) and AddSoftBody(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), SoftSubsteps and SoftIterations options. The SoftBody handle 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 scenes cloth-drape-3d, soft-pile-3d, soft-rigid-3d, soft-mesh-3d with soft-body metrics and a Jitter2 driver; fuzz targets world3d-softbodies and world3d-softbody-snapshot. DebugContent.All is now 1023; it is a constant, so code compiled against an earlier preview keeps All = 511 and does not draw soft bodies until it is rebuilt.

v0.3.0-preview.1

v0.3.0-preview.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 30 Oct 08:48
  • 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) and buoyancy-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() on PhysicsWorld2D and PhysicsWorld3D returns an upper bound on GetSnapshotSize() 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

v0.2.0-preview.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 29 Oct 23:38
  • 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> gains Translated(ReadOnlySpan<double>), which the exact origin shift uses. A type that implements IBox<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.

  • Warmup primes 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, OverlapSphereAll 0.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 of Fraction, Distance, Point and Normal of 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 = 0 restores the world-wide schedule); islands with joints keep Substeps. 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: BroadphaseMargin is 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.

  • ShiftOrigin is 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

v0.1.0-preview.6 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 29 Oct 11:07
  • Fix: 0.1.0-preview.5 could refuse a snapshot of a valid 3D world. Its Restore rejected 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 with SnapshotFormatException, 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 with BodyOutOfBoundsException (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 Restore refuses 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 AddVehicle and Restore refuse values outside the bounds with the usual argument and snapshot errors. The step also keeps engine and wheel speeds finite, and Restore checks 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

v0.1.0-preview.5 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 29 Oct 07:17

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.SlopeLimit and Character.SlopeLimit are SlopeLimitRadians (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 and foreach over the live bodies and characters in slot order with a struct enumerator that allocates nothing.
  • The 3D engine is [Experimental("OPHYS0002")]: every public type in RecursiveAI.OnePhysics.Physics3D. Using it raises the diagnostic until you suppress it (#pragma warning disable OPHYS0002 or <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.Build now 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 with HullBuildException; 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 Restore refuses 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 with SnapshotFormatException naming 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.