All notable changes to this project are recorded here. The format follows Keep a Changelog and the project uses Semantic Versioning. Before 1.0.0, any minor release could change the API, the ABI and the snapshot and journal formats. From 1.0.0 every 1.x release keeps the API, and an entry says what each changed result changes.
M3_ABI_VERSION, which now names
1.1, so 1.1.0 refuses a snapshot, tape or container that 1.0.x wrote
with m3_errorVersion. m3CreateWorld refuses a def from 1.0
headers the same way: rebuild against the 1.1 headers. The shared
library's SOVERSION is 1.1.m3World_Explode lay inside a shape, its rounding
included, the body was pushed from the blast toward a point of that
shape, with the impulse applied there. That point was a sphere's centre,
the middle of a capsule's segment, or the average of a hull's
vertices. A lopsided hull, or a body with several shapes, therefore
spun for no physical reason. A blast exactly at that point pushed
along the shape's own x axis. Now the push runs from the blast to the
body's centre of mass, as maul2d's does, so it spins nothing, and a
blast at the centre of mass itself pushes nothing; the body still
wakes. m3World_Explode, and a replay of a journaled explosion, can
give a different velocity to a body one of whose shapes holds the
blast. A shape the blast is outside of is pushed as before.
(#418)m3World_ReplayJournal replays.
(#413)m3World_Restore refused neither an occupied cell with
fill 0 nor an empty cell with a fill or a payload, and
m3World_ReplayJournal took the same states from a chunk create
record. Both now refuse such a chunk with m3_errorFormat, and
restore leaves the world as it was. No chunk the library writes is
refused.
(#409)m3_errorFormat.
(#415)m3_errorFormat, or m3_errorCapacity when it cannot allocate the
bake's scratch. No snapshot the library writes is refused. On the
largest mesh the limits allow, 65522 triangles, a restore took about
26 to 29 ms with the check against 22 ms without it.
(#416)M3_NODISCARD warns under GCC 14 in C23 too. The macro marks
every function that returns a status, so that a caller who drops one
gets a warning. In C it took the attribute only when
__STDC_VERSION__ was at least 202311L, but GCC 14 reports 202000L in
its C23 mode, so under GCC 14 the macro was empty and nothing warned.
It now takes the attribute in any dialect after C17 whose compiler has
it. A program built with GCC 14 in C23 that drops a status now gets
-Wunused-result, which fails a build with warnings as errors until
the call reads the status or says (void). Clang, C17 and C++ builds
see no change.
(#398)M3_SPEED_LIMIT along
its direction. The hold changes no step whose arithmetic stays finite,
however large its forces: such a step computes the same bits with it
as without it. The snapshot and journal formats are unchanged.
(#402)m3VoxelChunkShape_SetFill refuses an empty cell and records nothing,
but m3World_ReplayJournal took such a record and reported success,
though the record changed nothing. A tape the engine cannot write
passed as a genuine one. Replay now refuses it with m3_errorFormat
and leaves the world as it was, like any other record the engine never
writes. Tapes the engine wrote replay as before.
(#404)bool now carries
M3_NODISCARD, so a build of the library warns where its own code
drops such an answer, and the project's checks keep new ones marked.
No public declaration and no result changes, and a program sees
nothing new. The marks found the replay bug above, a voxel fill on an
empty cell.
(#405)m3CreateBody used to store a static def's velocities, and
m3Body_SetLinearVelocity and m3Body_SetAngularVelocity took a
static body. The body stayed where it was, but contacts read its
velocity and carried whatever touched it. Create now refuses a static
def whose velocity or spin is not zero (a negative zero counts as
zero) with m3_errorInvalid. The two setters refuse a static body,
whatever the value, and count one misuse. A journal replay refuses
the same calls, so a 1.0.x tape that gives a static body a velocity no
longer replays, and m3World_Restore refuses a 1.0.x snapshot in
which a static body moves, with m3_errorFormat. Results change only
for a host that gave a static body a velocity or called a velocity
setter on one. A shape's surfaceVelocity is the way to make a
conveyor.
(#393)m3Body_ApplyForce, m3Body_ApplyTorque and
m3Body_ApplyForceAtPoint refuse a disabled body as misuse, and so
does their replay. A body that stops moving drops the force and
torque it held, so it moves on from rest. m3World_Restore refuses,
with m3_errorFormat, a snapshot that holds a force or torque on a
body that is not an awake, enabled dynamic body. Besides a stuck
force, that refuses a 1.0.3 snapshot taken after a body with a
pending force turned kinematic and before the next step, which
cleared it. Impulses are unchanged. The step is not changed: a 1.0.3
snapshot that holds neither state nor a moving static body steps to
the same bits, so 1.0.4 restores it.
(#396)m3World_Restore used to accept one
in a forged or damaged snapshot. The restored body then reported its
velocity while asleep and resumed it when woken, where a body put to
sleep through the API starts from rest. Restore now refuses such a
snapshot with m3_errorFormat and leaves the world as it was,
comparing each component bit for bit, so a -0.0 is refused too.
Every snapshot the engine writes restores as before, and the check
leaves stepping as it was.
(#380)m3Body_SetAwake refuses any other type. m3World_Restore used
to accept a forged or damaged snapshot with a sleeping static or
kinematic body. It now refuses one with m3_errorFormat and leaves
the world as it was. Every snapshot the engine writes restores as
before, and the check leaves stepping as it was. A 1.0.2 snapshot
steps to the same bits, so 1.0.3 restores it.
(#392)m3_success. The replay now
applies exactly the vetoes each step recorded, as
m3World_SetPreSolveCallback promised; the m3PreSolveFunction
comment, which said to register the callback before replaying, was
the wrong one and is corrected. Results change only for a replay run
with a callback installed, which now lands on the recorded bits. A
1.0.0 snapshot steps to the same bits, so 1.0.1 restores it.
(#363)feat in a minor release, or as a fix in a patch
release when it corrects a result the documentation already
promised, and its changelog entry says what changes. There is no
2.0.0. The API and the results are those of 0.13.1.
(#355)M3_ABI_VERSION is now the major and minor version, so a program
built against 1.0.x headers runs on any 1.0.x library, and
m3CreateWorld refuses a def from another minor version's headers
with m3_errorVersion. The shared library's SOVERSION is 1.0,
and find_package(maul3d 1.0) accepts any later 1.x.
(#355)bindings/csharp/Maul3D.cs ships with every release; a name it binds
is never removed or renamed in 1.x. Bind more of the API in a file of
your own, so a later Maul3D.cs replaces this one. The Godot starter
kit stays outside the promise.
(#355)M3_ABI_VERSION, so 1.0.0 refuses a snapshot, tape or container
that 0.13.1 or any earlier release wrote with m3_errorVersion.
From 1.0.0 the patch releases of a minor version read each other's,
except that a release that changed a result refuses a snapshot
written before it.m3_errorVersion.m3Body_GetContactData and
m3Shape_GetContactData read 24 bytes before the world's transforms
array, and m3World_Draw and m3Shape_SetFilter read the same
missing body. Restore now refuses such bytes with m3_errorFormat
and leaves the world as it was. Snapshots the library writes still
restore. (#350)m3World_Draw with drawContacts read the body of each pair's first
shape before checking whether the pair touched. Drawing between a
shape's destroy and the next step therefore read in front of the
transforms array for each pair whose first shape was the dead one.
Nothing was drawn from those bytes. The draw now skips a pair with no contact points first
and draws the same primitives as before.
(#350)| 0.12.0 | 0.13.0 |
|---|---|
m3_voxelShape |
m3_voxelChunkShape |
m3Vehicle_GetCompression |
m3Vehicle_GetWheelCompression |
m3SoftBody_AnchorToSoft |
m3SoftBody_AnchorToSoftBody |
m3DrivetrainDef.autoShift |
enableAutoShift |
m3DrivetrainDef.diffMode, diffCouple |
differentialMode, differentialCoupling |
M3_MAX_DRIVETRAIN_CURVE |
M3_MAX_DRIVETRAIN_CURVE_POINTS |
M3_SERVO_REACH_LIMIT |
M3_TARGET_REACH_LIMIT |
M3_BLAST_LIMIT |
M3_EXPLOSION_IMPULSE_LIMIT |
(#344)
- Breaking: a dropped explosion carve has its own counter.
m3Counters.voxelEditsRefused counted both voxel edits refused with
m3_errorLimit and explosion carves m3World_Explode skipped for
the same surface bound, which no status reports. It now counts the
edits only, and the new m3Counters.voxelCarvesDropped counts the
skipped carves. A program that watched voxelEditsRefused for
carves reads voxelCarvesDropped.
(#346)
- Breaking: m3Counters drops three fields other calls report.
scratchPeak and scratchCapacity are gone: read them from
m3World_GetMemoryUsage. snapshotBytes is gone: call
m3World_GetSnapshotSize. m3World_GetCounters no longer measures
the snapshot.
(#347)
- Formats. The snapshot keeps format 6, the journal tape format 7
and the replay container format 4. As in every release, a snapshot,
tape or container written by another release is refused with
m3_errorVersion.
m3CreateSphericalJoint took a negative coneAngle while
enableCone was false, a value m3SphericalJoint_SetConeAngle
refuses with the cone on or off. The create now refuses it with
m3_errorInvalid and one misuse whether the cone is on or off, and
restore and replay refuse such bytes. Pass an angle of zero or more.
(#341)m3JointDef, m3DefaultJointDef and m3CreateJoint are gone. Each
of the twelve types has its own def, default and create:
m3SphericalJointDef with m3DefaultSphericalJointDef and
m3CreateSphericalJoint, and the same for the revolute, prismatic,
weld, distance, generic, wheel, filter, parallel, motor, gear and
pulley joints. Each def holds only the fields its type reads, so a
cone on a wheel or a spring on a weld can no longer be written.
Rename the fields you set as below. A create refuses another type's
def with m3_errorInvalid and one misuse. The default wheel def
builds: its suspension points down -y with its axle along z. The C#
JointDef, DefaultJointDef and CreateJoint are replaced by the
same twelve structs, defaults and creates.0.11.0 m3JointDef field |
0.12.0 |
|---|---|
type |
gone: call the create for your type |
lowerLimit, upperLimit |
spherical lowerTwist, upperTwist; revolute lowerAngle, upperAngle; prismatic and wheel lowerTranslation, upperTranslation |
wheel localAxisB |
localSpinAxisB |
genericLinear, genericAngular |
linearModes, angularModes |
genericLinearLower, genericLinearUpper |
linearLower, linearUpper |
genericAngularLower, genericAngularUpper |
angularLower, angularUpper |
genericMotorAxis |
motorAxis, M3_GENERIC_NO_MOTOR for none |
(#299)
- Breaking: a dynamic body bounds its contact lever ratio. A
colliding shape far from its body's center of mass, on a body a
joint also holds, let the contact go: past a ratio of about 1e7,
mass times the squared arm over the moment that arm turns, the
contact and the joint stopped settling and the body fell through.
M3_CONTACT_LEVER_LIMIT, 1e6, is the new bound. A shape create,
destroy, resize or density change, a filter change that lets a
shape collide, and a type change to dynamic are refused with
m3_errorLimit when they would carry the ratio past it. They change
nothing and count no misuse. Restore and replay refuse such bytes.
m3Shape_SetFilter now returns m3Result, so check its status.
(#302)
- Breaking: body names are UTF-8 with a byte count.
m3Body_SetName(world, body, name, byteCount) returns m3Result.
It refuses a name longer than M3_MAX_BODY_NAME_BYTES, 31 bytes,
with m3_errorLimit where it cut the name, and bytes that are not
well-formed UTF-8 or hold a zero byte with m3_errorInvalid. Each
refusal keeps the old name and counts one misuse. A count of 0
clears the name. m3Body_GetName(world, body, nameOut, capacity)
copies the name into your buffer with no terminator and returns its
byte length; call it with a NULL buffer and capacity 0 to measure.
(#293)
- Breaking: m3World_Compare returns a status. It takes
rowsOut, capacity and totalOut in place of out and
outCount, writes up to capacity rows and sets *totalOut to the
number of differing slots, which may exceed the capacity. Read
min(total, capacity) rows. Worlds of different capacities now
compare slot by slot instead of being refused. A refusal returns
m3_errorInvalid and counts one misuse, where it returned -1 and
counted none. m3EncodeReplay's buffer parameter is named
containerOut.
(#294)
- Breaking: the point test takes a filter, the capsule overlap a
center. m3World_TestPoint(world, point, filter) answers with the
lowest shape the filter sees; pass m3DefaultQueryFilter() to keep
the old answer. m3World_OverlapCapsule takes a center and two
points relative to it, as m3World_CastCapsuleClosest does, in
place of two world points.
(#297)
- Breaking: seven internal math helpers leave core_math.h.
m3MinF, m3MaxF, m3ClampF, m3AbsF, m3MakeZeroMat3,
m3UnwindAngle and m3IntegrateRotation are no longer declared.
Use your own. The vector, matrix and quaternion math, m3ComputeCosSin
and m3Atan2 stay.
(#298)
- Formats. The snapshot keeps format 6, the journal tape format 7
and the replay container format 4. As in every release, a snapshot,
tape or container written by another release is refused with
m3_errorVersion.
m3Joint_EnableMotor(world, joint, false) did not stop it. It now
drives only while the switch is on, like every other motor; set
enableMotor in the def to keep it driving.
(#301)m3World_TestPoint no longer
reports shapes of disabled bodies, and m3World_OverlapAabb and
m3World_OverlapSphere no longer report planes the filter hides or
planes on disabled bodies.
(#296,
#309)m3World_OverlapBox took any finite quaternion and answered for a
stretched or shrunk box. A rotation whose squared length is not
between 0.98 and 1.02 is now refused as the box cast refuses it: the
call returns 0, zeroes the array and counts one misuse.
(#306)m3World_Explode carved every voxel chunk in reach, including
chunks its filter hides and chunks on disabled bodies, while its
push already skipped both.
(#318)m3SphericalJoint_EnableCone, IsConeEnabled, SetConeAngle and
GetConeAngle switch and move the swing cone without recreating the
joint, so a ragdoll keeps its warm impulses. Each call wakes the
joint's bodies. The angle must be finite and not negative, and
EnableCone refuses to switch the cone on over a negative angle a
def left. Any other joint type or a stale id is refused with one
misuse, and the getters then read false and 0.
(#205)m3CreateHeightFieldShape still compiles and now builds the
native grid, so a program that wants the triangle mesh must call
m3CreateHeightFieldMeshShape.| 0.10.0 | 0.11.0 |
|---|---|
m3AabbResult in body.h |
m3WorldAabb in world.h |
m3Joint_GetConstraintForce, GetConstraintTorque |
m3Joint_GetReactionForce, GetReactionTorque |
m3Joint_SetBreakThresholds |
m3Joint_SetBreakLimits |
m3_fixedJoint, value 3 |
m3_weldJoint, value 3 |
m3CreateHeightFieldGridShape |
m3CreateHeightFieldShape |
m3CreateHeightFieldShape, the triangle mesh |
m3CreateHeightFieldMeshShape |
m3CreateSoftBodyTet |
m3CreateTetSoftBody |
M3_MOVER_PLANES |
M3_MAX_MOVER_PLANES |
m3World_OverlapHullPoints |
m3World_OverlapHull |
m3FragmentEvent's comChunk, comWorld, boundsLo, boundsHi |
localCenter, worldCenter, lowerBound, upperBound |
m3WaterVolumeDef's lo, hi |
bounds.lowerBound, bounds.upperBound |
m3WheelDef's hertz, zeta |
springHertz, springDampingRatio |
m3Counters' misuse, allocCalls, freeCalls, heldPushRetakes |
misuseCount, allocCount, freeCount, heldPushRetakeCount |
m3Body_GetRotationalInertia |
m3Body_GetCentralInertia |
C# JointType.Fixed |
JointType.Weld |
Godot substeps property |
substep_count |
m3Joint_GetBreakLimits is new and reads back what
m3Joint_SetBreakLimits set, 0 for a cap that is off.
(#207,
#255,
#256,
#257,
#258,
#263)
- Breaking: the distance joint has its own range, spring and rest
length. Its range is m3JointDef.minLength and maxLength, set
with m3DistanceJoint_SetLengthRange and read with
GetLengthRange. Its spring is switched by the def's enableSpring
with springHertz and springDampingRatio, and by
m3Joint_EnableSpring and the spring setters at run time. It pulls
toward m3JointDef.length, set with m3DistanceJoint_SetLength,
which need not lie inside the range. The joint takes no limit and no
motor, so a def that still sets the range through enableLimit or
the spring through enableMotor is refused at create, as are the
limit and motor calls on it. The def's spring fields now switch the
spring at create on every joint type that takes one. The C#
JointDef gains MinLength, MaxLength, Length, EnableSpring,
SpringHertz and SpringDampingRatio.
(#191,
#202)
- Breaking: kinds and modes are fixed-width integers.
m3BodyType, m3ShapeType, m3JointType and m3AxisMode are
int32_t typedefs with the same named values, so source that uses
the names compiles as before. m3DrivetrainDef.diffMode is an
m3DifferentialMode: m3_differentialOpen,
m3_differentialLimitedSlip or m3_differentialLocked. The generic
joint's modes are m3AxisMode arrays, so m3JointDef grows from
248 to 272 bytes. The C# defs hold BodyType, JointType and a new
AxisMode enum.
(#282)
- Breaking: refusals a correct program can meet return a status.
These calls carry M3_NODISCARD and return m3Result:
- m3VoxelChunkShape_SetVoxel, ClearVoxel, SetFill and
ClearBox return m3_errorLimit when the chunk's surface would
pass M3_MAX_VOXEL_SURFACE_BOXES, 2048 boxes, and change nothing.
ClearBox writes its count through a new int32_t* clearedOut.
- m3SoftBody_PinParticle, AnchorParticle and AnchorToSoft
return m3_errorLimit for a full table of
M3_MAX_SOFTBODY_ANCHORS.
- m3Body_SetType, m3Body_SetTargetTransform,
m3Shape_SetDensity, m3Shape_SetSphere and m3Shape_SetCapsule
return m3_errorLimit for a body mass past M3_MASS_LIMIT, a
joint past its lever ratio, a type change the body's shapes, joints
or speed refuse, or a servo target out of reach.
A refusal the world's state decides no longer counts misuse, in
these calls and in the shape and joint creates and m3DestroyShape.
A stale id is m3_errorStale, a density past M3_DENSITY_LIMIT
m3_errorLimit and a bad argument m3_errorInvalid, each still
counting one misuse.
(#265,
#266,
#268)
- Breaking: m3World_Step returns m3_errorLimit past its
published bounds. A dt below M3_MIN_STEP or above
M3_STEP_LIMIT, an infinite one included, and more than
M3_MAX_SUBSTEPS substeps were m3_errorInvalid. A NaN dt, a dt of
zero or below and fewer than one substep stay m3_errorInvalid.
Each still counts one misuse. The step's second parameter is named
substepCount.
(#283,
#263)
- Breaking: list calls take the filter before the array and return
the true total. m3World_OverlapAabb, OverlapSphere,
OverlapCapsule, OverlapBox, OverlapHull and CastRayAll take
the query filter before the output array. m3World_OverlapAabb
takes one m3WorldAabb in place of its two corners. Those six,
m3World_CollideMover, m3Body_GetContactData and
m3Shape_GetContactData write at most capacity entries and return
the total, which may exceed the capacity, where they returned the
count written. A NULL array with capacity 0 measures. A NULL array
with a positive capacity, or a negative capacity, is refused with one
misuse by these calls and by m3World_GetBodies, GetJoints,
m3Body_GetShapes and GetJoints, which took both before.
(#207,
#216)
- Breaking: a refused list call or snapshot zeroes the caller's
buffer. Every list call above and m3World_Compare zero their
array over its whole capacity when they refuse, a stale id included,
so an earlier answer is never read as this one. A successful answer,
an empty one included, writes only its entries. A refused
m3World_Snapshot zeroes its buffer over its capacity, so a host
that rings snapshot buffers should size each with
m3World_GetSnapshotSize first.
(#259)
- Breaking: one hit type for every cast. m3CastHit
{shapeId, point, normal, fraction} replaces
m3RayCastResult and m3RayHit. m3World_CastRayClosest,
CastSphereClosest, CastCapsuleClosest, CastBoxClosest,
CastHullClosest and CastMover return bool and take
m3CastHit* hitOut last. Each clears it first, so a miss leaves
zeros, and a NULL hitOut returns false with one misuse.
m3World_CastRayAll fills m3CastHit. The C# RayCastResult is
CastHit, and CastRayClosest takes out CastHit hit.
(#211)
- Breaking: sensor events name the sensor, the visitor and the
step. m3SensorEvents holds arrays of m3SensorBeginEvent and
m3SensorEndEvent, each {sensorShapeId, visitorShapeId,
step}, in place of the contact event structs with shapeIdA and
shapeIdB. step is the world's step count before the step, so the
first step's events carry 0.
(#217)
- Breaking: snapshot, journal and replay sizes are 64-bit, with no
cap. m3World_GetSnapshotSize, m3World_Snapshot,
m3World_Restore, m3World_StartJournal, m3World_StopJournal,
m3World_ReplayJournal, m3DescribeJournal,
m3GetEncodedReplaySize, m3EncodeReplay and m3DecodeReplay take
and return int64_t sizes, as do m3ReplayView's sizes,
m3JournalInfo's counts and m3Counters.snapshotBytes, and the C#
snapshot calls take long. M3_MAX_SNAPSHOT_BYTES is removed, and
m3CreateWorld no longer limits mesh and hull slots, and a height
field grid's create no longer refuses samples, to keep a snapshot
under 2 GiB. On a 32-bit target a size past
SIZE_MAX is m3_errorInvalid.
(#226)
- Breaking: what a failed call leaves in bytesOut.
m3World_Snapshot and m3World_StopJournal write 0 when the world
is NULL, where they left it. A stop refused as busy, from the world's
own callback, leaves it as it was, where it wrote 0.
m3EncodeReplay leaves it as it was on any failure, where it
wrote 0.
(#210,
#215)
- Breaking: m3IsCpuBackendSupported returns bool in place of
an int32_t 1 or 0, and m3Hash64 takes an int64_t byte count.
(#260)
- Breaking: joints solve their coupled rows as one block. A
slider, weld or generic weld whose anchor sits far from its body's
center of mass gave way by metres: on a 100 m arm a slider sagged
2.4 m off its axis in a second. It now holds within 1e-4 m. The
prismatic, weld, revolute, wheel and generic joints step to
different results, and the joint-heavy bench scene costs about 18%
more a step, the others about 5%.
(#232)
- Breaking: m3Counters.awakeCount counts only enabled bodies. A
body disabled while awake keeps its awake flag and was counted,
though it takes no part in the step.
(#287)
- Formats. Journal tapes are format 7 and the replay container is
format 4, with a 56-byte header. As in every release, a snapshot,
tape or container written by another release is refused with
m3_errorVersion.
(#191,
#202,
#205,
#226,
#282)
m3real typedef. Every call and field that used
it takes float, the same type, so replace the name and nothing else
moves.
(#262)m3DestroyWorld walked
pools already freed, a use after free under AddressSanitizer. The
hook now finds an empty world, and every writer it calls is refused
as busy.
(#285)m3EncodeReplay copied into an output that overlapped the snapshot
or the tape, which C leaves undefined. It now refuses with
m3_errorInvalid and writes nothing.
(#281)m3_errorStale. It
returns m3_errorInvalid.
(#275)m3World_StopJournal on an overflowed journal returned
m3_errorState with one misuse, where its documentation says
m3_errorCapacity. It returns m3_errorCapacity and counts none.
(#221)enableCone on every type. On a wheel it
switched steering on at 0 Hz, and the world's own snapshot was then
refused by its restore. Only the spherical joint takes it now.
(#204)m3World_Restore accepted values no call can write, and now refuses
them with m3_errorFormat: a motor joint aim past 4e15 m on any
axis, a nonzero third drive value on a joint type that never writes
it, and a touching pair of two sensors, which the next step reported
as a sensor event.
(#198,
#201,
#225)m3World_Restore took the body name slots without a check, so a
snapshot whose name slot held no zero byte restored, and
m3Body_GetName then returned text that ran past its slot. Restore
refuses such a slot with m3_errorFormat.
(#292)m3replay sized a container with a 32-bit offset on Windows and
32-bit Linux, so a container past 2 GiB read as invalid. It uses a
64-bit offset and names each read failure.
(#231)m3WheelJoint_IsSteeringEnabled,
m3WheelJoint_GetTargetSteeringAngle, GetSteeringHertz,
GetSteeringDampingRatio and GetMaxSteeringTorque read back what
the steering setters store.
(#188)Get, and
every on/off switch is an Enable / Is...Enabled pair. Hook types end
in Function. Rename your calls as below. Each one does what the old
name did, except m3Body_Enable, which now takes a bool, and steering,
covered in the next item.| 0.9.0 | 0.10.0 |
|---|---|
m3TaskFn, m3EnqueueTaskFn, m3FinishTaskFn |
m3TaskFunction, m3EnqueueTaskFunction, m3FinishTaskFunction |
m3PreSolveFn |
m3PreSolveFunction |
m3ResultName |
m3GetResultName |
m3CpuSupportsBackend |
m3IsCpuBackendSupported |
m3World_SnapshotSize |
m3World_GetSnapshotSize |
m3Body_Enable and m3Body_Disable |
m3Body_Enable with true or false |
m3BodyDef.isEnabled |
m3BodyDef.enable |
m3Body_SetBullet, m3Body_IsBullet |
m3Body_EnableBullet, m3Body_IsBulletEnabled |
m3BodyDef.isBullet |
m3BodyDef.enableBullet |
m3Shape_EnablePreSolve, m3Shape_IsPreSolveEnabled |
m3Shape_EnablePreSolveEvents, m3Shape_IsPreSolveEventsEnabled |
m3Shape_SetMeshMaterials |
m3MeshShape_SetMaterials |
m3VoxelChunk_SetVoxel, ClearVoxel, ClearBox, SetFill |
m3VoxelChunkShape_SetVoxel, ClearVoxel, ClearBox, SetFill |
m3Joint_SetTargetAngle |
m3RevoluteJoint_SetTargetAngle |
m3Joint_SetTargetRotation |
m3SphericalJoint_SetTargetRotation |
m3Joint_SetSteer |
see the next item |
m3Joint_GetSteerAngle |
m3WheelJoint_GetSteeringAngle |
m3Vehicle_SelectGear |
m3Vehicle_SetGear |
C# Native.ResultName |
Native.GetResultName |
C# Native.SnapshotSize |
Native.GetSnapshotSize |
C# BodyDef.IsBullet |
BodyDef.EnableBullet |
C# BodyDef.IsEnabled |
BodyDef.Enable |
Struct layouts and the snapshot format are unchanged. The journal stays
at format 3. (#188)
- Breaking: wheel steering has a switch and a setter per value.
m3Joint_SetSteer(enable, targetAngle, hertz, zeta, maxEffort) becomes
m3WheelJoint_EnableSteering(enable) and four setters:
m3WheelJoint_SetTargetSteeringAngle, SetSteeringHertz,
SetSteeringDampingRatio and SetMaxSteeringTorque. zeta is now
the damping ratio, and the budget is a torque, 0 for no cap. Steering
keeps its values while it is off. As before, enabled steering needs a
positive frequency. Because the switch is now a call of its own, set
the frequency first and then enable steering: enabling it before any
frequency is set is refused and counts one misuse. Each call records
its own journal entry. A tape that steers is therefore longer than the
same scene recorded with 0.9.0, and its bytes hash differently, while
the simulation itself is unchanged.
(#188)
m3Joint_IsLimitEnabled
and m3Joint_GetLimits, m3Joint_IsMotorEnabled,
m3Joint_GetMotorSpeed and m3Joint_GetMaxMotorEffort, and
m3Joint_IsSpringEnabled, m3Joint_GetSpringHertz and
m3Joint_GetSpringDampingRatio read back what the setters store.
(#185)m3MotorJoint_GetMaxForce,
GetMaxTorque, GetLinearOffset and GetAngularOffset.
(#187)m3Joint_SetLimits(enable, lower, upper) becomes
m3Joint_EnableLimit(enable) and m3Joint_SetLimits(lower, upper).
m3Joint_SetMotor(enable, speed, maxEffort) becomes
m3Joint_EnableMotor, m3Joint_SetMotorSpeed and
m3Joint_SetMaxMotorEffort.
m3Joint_SetSpring(enable, hertz, dampingRatio) becomes
m3Joint_EnableSpring, m3Joint_SetSpringHertz and
m3Joint_SetSpringDampingRatio. A part keeps its values while it is
off. A limit range must be ordered when it is set and whenever the
limit is on, and a spring may hold 0 Hz while it is off. Gear, pulley,
fixed, parallel, filter, generic and motor joints now refuse limits
instead of ignoring them.
(#185,
#187)m3JointDef.maxForce and maxTorque, set at
run time with m3MotorJoint_SetMaxForce and m3MotorJoint_SetMaxTorque,
instead of lowerLimit, upperLimit and m3Joint_SetLimits.
m3Joint_SetMotorPose(offset, rotation) becomes
m3MotorJoint_SetOffsets(linearOffset, angularOffset). m3JointDef
grows from 216 to 224 bytes, and journal tapes use format 3, so tapes
from earlier releases are refused. The C# JointDef gains MaxForce
and MaxTorque.
(#187)m3JointDef.collideConnected
becomes enableConnectedCollision, with
m3Joint_EnableConnectedCollision and
m3Joint_IsConnectedCollisionEnabled in place of
m3Joint_SetCollideConnected and m3Joint_GetCollideConnected.
m3WorldDef.enableSleeping becomes enableSleep, with
m3World_EnableSleep and m3World_IsSleepEnabled in place of
m3World_EnableSleeping and m3World_IsSleepingEnabled. The C#
fields follow. The renames alone leave the def layouts and the
snapshot unchanged.
(#182)m3Joint_SetMotor wrote into the motor slot of any joint. On a gear it
overwrote the bodies' reference spins, so meshed gears at rest turned
to a new relation, and past a half turn the world's snapshot could no
longer be restored. On a motor joint it moved the servo's aim. Only
revolute, prismatic, wheel, generic and distance joints take a motor
now, and the motor calls refuse every other type.
(#184)m3WorldDef.pairCapacity and the dropped pair count. The pair
table's size is now set by the def: 0 takes 8 pairs per shape, the
old fixed size, and the bound is M3_MAX_PAIR_CAPACITY. When a step
finds more overlapping pairs than the table holds, it keeps the pairs
of the lowest keys, drops the rest for that step, and counts them in
m3Counters.pairsDropped. A program that sees the count grow should
raise pairCapacity.
(#167)m3World_Step returns m3Result and must not be
discarded. It returns m3_errorInvalid for a null world or a time
step or substep count out of range, and m3_errorBusy from host code
the world is running. A world now takes its whole step scratch at
create, sized at its capacities, so a step never asks the allocator
and can no longer stall for memory. m3CreateWorld needs about
7.2 KiB more per shape slot than before, 14.5 MiB more for the default
world, and refuses with m3_errorCapacity a world whose scratch would
pass 2 GiB. m3World_GetMemoryUsage gives the exact figure. The C#
binding's Step returns Result.
(#165)m3_success. The snapshot format moves to 6: its header is
60 bytes and carries the pair capacity, so snapshots from earlier
releases are refused with m3_errorVersion.
(#167)m3_errorFormat (-8) is returned for malformed snapshot, journal tape and replay
container bytes, and counts no misuse. m3_errorLimit (-9) is
returned by a create, m3CreateWorld, m3Character_SetStance and
m3DestroyShape for an argument past a published bound, and counts
one misuse; a NaN stays m3_errorInvalid. m3_errorBusy (-10) is
returned by every writer refused because a step, a query, a draw or
a library callback is running on the world, and counts one misuse.
Every fixed bound a call refuses is now a constant in
maul3d/base.h or in the soft body and vehicle headers. Code that
compared against m3_errorState or m3_errorInvalid for these
cases must compare against the new statuses.
(#141)M3_SOFTBODY_MAX_PARTICLES, _EDGES, _ANCHORS and _TETS become
M3_MAX_SOFTBODY_PARTICLES, _EDGES, _ANCHORS and _TETS;
M3_SOFTBODY_MAX_PRESSURE becomes M3_SOFTBODY_PRESSURE_LIMIT;
M3_VEHICLE_MAX_WHEELS becomes M3_MAX_VEHICLE_WHEELS; and
M3_DRIVETRAIN_MAX_CURVE and _GEARS become
M3_MAX_DRIVETRAIN_CURVE and _GEARS.
(#141)m3JointDef drops genericReserved and shrinks from
224 to 216 bytes, and m3FragmentEvent drops pad. Journal tapes
use format 2, so tapes from earlier releases are refused.
(#139)M3_GEOMETRY_LIMIT, 1e8 m, of their body, and height field and
voxel cells within M3_CELL_LIMIT, 1e5 m. Create and replay return
m3_errorLimit past either bound, and restore refuses such content
with m3_errorFormat. M3_MAX_WATER_VOLUMES moves to
maul3d/world.h.
(#152)m3_errorBusy, changes nothing and counts one
misuse.
(#160)m3_errorInvalid and
counted a misuse, as for a degenerate cloud. It now returns
m3_errorCapacity and counts nothing.
(#161)m3Joint_SetTargetTranslation on a wheel joint zeroed its steering's
frequency, damping and budget, so the wheel stopped steering and its
world's snapshot was refused by restore. The steering now keeps its
values.
(#175)m3_errorFormat.
(#179)m3_errorInvalid and counts one misuse,
and so does a shape create on a jointed body. m3DestroyShape,
m3Shape_SetSphere, m3Shape_SetCapsule, m3Shape_SetDensity and
m3Body_SetType refuse a change that would carry a joint past the
limit, change nothing and count one misuse. Journal replay and
restore refuse it with m3_errorInvalid. No result changes within
the limit.
(#118)m3CreatePlaneShape refuses an offset past 1e15 m, and
m3CreateSoftBody and m3CreateSoftBodyTet refuse a particle past
1e15 m on any axis. The step now holds every body and soft particle
within 1e15 m, the position limit every create keeps, rather than at
1e18 m, and restore bounds positions there.
(#123)M3_MAX_SNAPSHOT_BYTES
(INT32_MAX) and sized in 64 bits. m3CreateWorld refuses a def
whose capacities could pass the bound with m3_errorCapacity before
allocating anything; the guide gives the bytes per slot, near
278,000 shape slots or 1,638 mesh slots with nothing else.
m3CreateHeightFieldGridShape refuses a grid that would carry the
snapshot past the bound with m3_errorCapacity, and replay refuses
it with m3_errorInvalid. Before, the snapshot size was counted in
32 bits and a world large enough wrapped it. No result changes.
(#126)M3RP in place of
M3J1. Each magic reads in file order, so a hex dump starts with
M3SN, M3JN or M3RP. Bytes from another release, or a snapshot
from another build, are refused with m3_errorVersion instead of
m3_errorInvalid, and a container holding another release's
snapshot or tape is refused the same way. m3World_StartJournal
now needs a buffer of at least 16 bytes, and m3World_ReplayJournal
refuses zero bytes as m3_errorInvalid instead of replaying them as
an empty tape. m3GetEncodedReplaySize returns 0 for sizes that
cannot be a snapshot and a tape, and for a container past
INT32_MAX bytes. A host keeps assets, not state, across an
upgrade; docs/internals/state-formats.md gives the layouts
(#109,
#110).m3Counters.heldPushRetakes counts each push a character, a soft
particle or a wheel lands that is taken again in double because the
float result was not finite. Like voxelEditsRefused, it is a
counter, not state: it is not hashed, snapshotted or journaled, and
a restore leaves it as it is
(#111).m3_errorInvalid, no misuse counted and the world left as it was.
Every world the API builds still restores, and no golden line moves
(#114,
GHSA-mg76-rhjf-fr34).capacity × 65536 creates over a world's life: a 1000-slot pool
creating and destroying 1000 objects a second ran out after about 18
hours. A pool of capacity n now makes n × 2^32 objects, about 136
years in that case. All 11 pools change, the internal hull, mesh,
height field and voxel pools included, and so do the generations
stored for a character's ground body, a vehicle's chassis and soft
body anchors. Public ids, the API and the ABI are unchanged. A
format 3 snapshot, or a replay container that starts with one, is
refused; take snapshots again with this release. Every golden hash
except the journal tape's moves, because the world hash covers the
wider generations
(#92).mass * arm^2 / moment below about 1e15. A slider on
a 1000 m arm holds again at 30° below its axis and above it, where it
was flung hundreds of metres; it still sags about 2 m, and is still
flung at 45° and 60° below its axis
(#96).
Results of ordinary systems are unchanged bit for bit. Long-arm
joints cost up to about 22% more instructions, and ordinary joint
chains within 3%
(#97).m3_errorInvalid (a create returns the null id), counts one
misuse, journals nothing and changes nothing, and replay refuses the
same values through the same checks. Among the limits: speeds within
1e5 m/s and rad/s per component (a kinematic body's linear speed
within 1e10 m/s), gravity within 1e4 m/s^2, frequencies within 1e5 Hz,
dimensionless ratios within 1e4, step lengths from 1e-4 to 10 s,
forces and torques within 1e20, and on a dynamic body every
mass-bearing shape at least 1e-6 kg with every principal moment at
least 1e-12 kg m^2 and the body at most 1e20 kg. The guide's
"Magnitude limits" chapter and the header docs list every limit per
call. A body holding static-only scenery cannot leave static, and a
teleport or a type change cancels a pending servo target
(#81).m3Shape_SetDensity always rebuilds the body's mass. It
takes no updateBodyMass argument, and its journal record shrinks
from 16 to 12 bytes. A body's mass, center and inertia are now always
derived from its type and shapes
(#82).-msse2 -mfpmath=sse), so it needs a processor with SSE2, and every
build now leaves trapping math at the compiler's default, from the
family's compiler flags. On AVX2 the solver's min and max are one
instruction each, with the same bits. Every golden scene and bench
hash is unchanged on x64 and i686, with GCC and Clang
(#85).m3Shape_GetBody gives its
body, so a program could destroy, retype or reshape either one, or add
a shape to the body, while the character lived on, which the character
does not support. m3DestroyShape, m3DestroyBody, m3Body_SetType,
m3Shape_SetSphere, m3Shape_SetCapsule and a shape create on the
character's body now refuse them with m3_errorInvalid, count a
misuse and change nothing; replay refuses the same ops in a tape.
Destroy the character instead: that still takes its body and capsule
with it, and m3Character_SetStance remains the way to resize it.
Replay also refuses a body type the public call refuses (outside
static, kinematic and dynamic); it used to set any byte.
(#73)m3_errorInvalid like every other restore
refusal, and the world is left as it was. Nothing is rebuilt, so
restored worlds still resimulate bit for bit, and every snapshot this
engine writes still restores, also one taken between a call and the
next step. The snapshot format is unchanged.
(#70)m3World_TestPoint reports a point far from a voxel chunk as
outside it. The cell of a point's chunk-local coordinates is now
bounded like every other grid-cell cast, and the library's other
float-to-int conversions were checked the same way. No result changes
for any value in range, and no golden or benchmark hash moved.
(#71)docs/api.md, which showed an older API, is
gone. m3World_Hash's doc now lists what the hash covers and what it
leaves out: mesh geometry and height field samples are not hashed.
m3Counters.voxelEditsRefused is documented as a counter that is
neither hashed nor snapshotted.
(#46)linux-i686-gcc-14 and
linux-i686-clang-20 presets build for i686 with
-m32 -msse2 -mfpmath=sse, and CI runs both. A host's own i686 build
needs those flags too: the library refuses to build with x87 float
arithmetic. base.h adds M3_ALIGN8, which the public headers use to
give 64-bit members 8-byte alignment.
(#41,
#44)m3Pos3, m3QueryFilter and the body, character, soft body, vehicle
and shape defs are now 8-byte aligned there, as on every other target.
These public structs change size on i686:m3BodyDef, 108 to 120 bytesm3CharacterDef, 68 to 72m3ShapeDef, 92 to 104m3VehicleDef, 460 to 464m3WaterVolumeDef, 76 to 80m3JointDef, 216 to 224m3RayCastResult, 52 to 56m3BodyMoveEvent, 52 to 56m3FragmentEvent, 68 to 72Every other public struct that holds an aligned member keeps its size
and changes only its alignment, from 4 to 8. An i686 tape recorded
with 0.1.x is refused with m3_errorInvalid when it holds one of the
twelve journal records whose size changed: a body, character, vehicle,
water volume or joint create, any shape create, a filter change, or a
force or impulse at a point. Other i686 tapes, and every i686
snapshot, still load. On x86-64, arm64, Windows and wasm nothing
changes but tape padding (below): existing tapes and snapshots load as
before.
(#44)
-m32 build on an x86-64 host
was built for AVX2. It is now chosen only for 64-bit builds.
(#41)m3Counters.voxelEditsRefused. A chunk's surface holds at most
2048 merged boxes, the checkerboard's count. An edit whose surface
would pass that bound changes nothing, journals nothing and counts no
misuse. It counts here instead, and m3VoxelChunk_ClearBox returns -1.
An explosion skips that chunk's carve and still applies its impulses.
A chunk create past the bound is refused with m3_errorCapacity, a
snapshot chunk past it with m3_errorInvalid, and a replayed edit past
it refuses the replay, which leaves the world as it was. No grid
measured so far reaches the bound. The field is a counter, not
state: it is not hashed, and a restore or a replay's back-out leaves
it as it is. A program that wants to know whether an edit landed can
compare it before and after.
(#38)m3CreateVoxelChunkShape returns m3_errorCapacity when the
allocator refuses the slot's tree, with the world as it was. A restore
takes the tree for each live chunk slot that has none yet, and returns
m3_errorCapacity when refused. m3MemoryUsage.contentBytes counts
the slot trees. A replay's up-front headroom no longer counts voxel
surfaces, only arrays sized by the mesh, shape and voxel capacities.
Budget 84 KB per voxel slot a program fills, on top of the
world's 88 KB.
(#38)m3CreateWorld returns
m3Result and hands the world back through an out parameter, and
every call takes the world first; readers take const m3World*.
m3WorldId, m3World_IsValid, m3Body_GetWorld, m3Shape_GetWorld,
m3Joint_GetWorld, m3SetAllocator, m3SetAssertHandler and
m3LastResult are removed; the allocator is part of m3WorldDef,
which begins with internalValue, version and loses workerCount.
Every create and destroy returns m3Result, with the id in an out
parameter (null on failure). A destroy of a stale id returns
m3_errorStale, and a destroy of the null id returns m3_errorInvalid.
Ids are {index1, generation}. Snapshots, restores, the journal calls
and the replay container codec return m3Result, and
m3World_RebuildBroadphase does too. Journal tapes recorded with
0.0.1 no longer replay, and the replay container is version 2.
(#13)m3World_Restore
refuses format 1, and replay files that hold one, with
m3_errorInvalid.
(#34)m3_errorInvalid, and they count as misuse.
This covers coordinates, half extents and local positions beyond 1e4,
densities above 1e6, non-finite joint limits and rope lengths, soft
body pressure above M3_SOFTBODY_MAX_PRESSURE, and the other bounds
the headers list. A stale id or a refused argument counts as misuse in
every call. A writer called while Step or Draw runs changes
nothing and counts as misuse.
(#14,
#19)m3World_Restore
leaves the world unchanged and returns m3_errorInvalid for content
the builders would not make (hull planes, mass and vertices; height
field and mesh content), and m3_errorCapacity when the allocator
refuses. Before, it could land such content or leave the world half
restored. m3World_ReplayJournal refuses an op the public call would
refuse. It returns m3_errorCapacity with the world as it was when
memory runs out. To do that it reserves its memory up front, so a
replay that fit a tight allocator before can now be refused before any
op runs.
(#27,
#29,
#30,
#33)m3_errorCapacity, where before it was created and collided with
nothing.