Repository navigation
Conversation
…nd headers Included third party libraries
Must be adapted for 26.3 updates
Refactor PhysX module includes to new server paths (servers/physics_server_3d.h -> servers/physics_3d/physics_server_3d.h) and update rendering server includes to servers/rendering/rendering_server.h across multiple headers and source files. Adjusted register_types to use physics_3d wrapper. Added two thirdparty PhysX vehicle steering headers (PxVehicleSteeringFunctions.h, PxVehicleSteeringParams.h) to provide steering-related declarations. These changes align with server subtree reorganization and supply missing vehicle steering definitions.
…tact + teardown fixes Features: - SoftBody3D: full simulation via GPU PxDeformableVolume (FEM, conforming tetrahedron cook) with a CPU XPBD fallback; per-body Auto/CPU/GPU resolution through physics/physx_3d/soft_body/mode or the physx_soft_mode node metadata - PhysXGranular3D: granular volume (sand/gravel/snow) with a real angle of repose (MLS-MPM Drucker-Prager grains, optional PBD-granular mode) - PhysXParticleFluid3D: MLS-MPM compute-shader solver mode (no CUDA required) with analytic colliders, rigid-body reaction impulses and GPU isosurface; GPU fluid gains granular mode and isosurface tuning/clip-warning fixes - Tests: node-level smoke test in GDScript and C#; soft-body behavioral tests un-skipped; suite 332/332 green; shaders moved to particles/shaders/ - README: refresh the PhysX section (5.10 SDK zips, D3D12 build steps, feature/status tables, milestone checklist) [Fix - Critical]: PhysXSpace3D: size the GPU deformable contact buffers (maxDeformableSurfaceContacts / maxDeformableVolumeContacts) — with the budgets at 0 the GPU pipeline silently dropped every deformable contact, so GPU soft bodies fell through the world [Fix - Critical]: physx_filter_shader: exclude deformable pairs from contact modification — the GPU deformable pipeline cannot contact-modify, and any pair carrying the flag was dropped entirely (second half of the soft-body fall-through) [Fix - Critical]: worldscape_3d editor: remove queue_free-from-destructor patterns across 11 destructors that crashed the editor at shutdown [Fix - Critical]: PhysXSpace3D: detach registered soft bodies on space destruction — a soft body outliving its space (RID never freed) crashed the engine with a use-after-free at shutdown [Fix - High]: joints: skip Joint3D's transient single-node configuration quietly (a static-vs-world joint is invalid in PhysX) and cache params/flags until the joint exists — removes error spam from every static+dynamic joint setup [Fix - High]: joints: clamp cone-twist and 6DOF swing/twist limits into PhysX's valid range — degenerate Godot values (0 span = locked, the stock 0/0 g6dof ranges) were rejected as "limit invalid", breaking ragdoll and bridge joints [Fix - Medium]: bodies: skip the PhysX-rejected setWakeCounter on kinematic dynamics (PhysX error on every CharacterBody3D spawn)
…fer size error [Critical] KinematicCollision3D::get_collider index error - body_test_motion returned hit || recovered — it reported a hit with an empty collision list whenever initial-penetration recovery occurred, even without recovery_as_collision. Godot's reference implementation only returns true when actual contacts were recorded (result_count > 0). CharacterBody3D.move_and_slide stores every returned result as a slide collision, so walking around with tiny floor penetration handed the demo a slide collision with zero contacts → get_collider(0) spammed the error every frame. - Fix (physx_direct_space_state_3d.cpp): return hit || (recovery_as_collision && recovered && collision_count > 0), and removed the lines that zeroed collision_count/collision_depth after the contact pass. Also updated the PHYSX-MOVE-004 C# test, which had enshrined the buggy behavior — it now sets RecoveryAsCollision = true (exactly what CharacterBody3D does) and additionally asserts the reported collision has contacts. [Critical] MultiMesh buffer size: set_particle_count() / set_foam_particle_count() only stored the new value. The PBD path sizes its MultiMesh and the server-side capacity at creation, so changing the count on a live fluid — the sand demo's 1–3 keys (40k/70k/100k) — made the render pass write a new_count × 12 buffer into a MultiMesh allocated for the old count → p_buffer.size() != instances * stride. Fix (physx_particle_fluid_3d.cpp): both setters now rebuild the live PBD fluid so capacity and MultiMesh match the new count (MPM path already reconfigures on the next spawn). Verified: changing count on a running fluid no longer errors and re-spawning works.
This change preserves simulation collision for convex shapes when a body is rebuilt or re-enabled, while keeping concave shapes query-only on non-kinematic dynamic actors. It also adds regression tests covering static↔rigid round-trips and disable/re-enable behavior for convex and concave shapes. modules/physx/objects/physx_body_3d.cpp — companion guard. set_shape_disabled() unconditionally restored eSIMULATION_SHAPE on re-enable, which would push that forbidden flag combination onto a concave shape attached to a non-kinematic dynamic body. Re-enable now restores the simulation flag only when the shape is convex (or the actor is static/kinematic); eSCENE_QUERY_SHAPE is always restored so the shape stays raycastable. modules/physx/test/project/tests/csharp/BodyTests.cs — two new tests. PHYSX-BODY-056: static→rigid→static→rigid round-trip with a convex box, dropped onto a static floor after each switch; asserts it comes to rest at the half-extent height each time. PHYSX-BODY-057: disable/re-enable cycle — convex shape restores collision (body lands on the floor again); a concave mesh on a dynamic body stays query-only (ray still hits it, body never becomes solid).
Add ray-pickability support and return extra cast rest info. - Introduce virtual is_ray_pickable() in PhysXObject3D (default true) and mark Body/Area/SoftBody overrides as override. Make Area raw-server default pickable (Area3D node overrides on ready). - Add pick_ray flag to PhysXQueryFilterCallback and enforce it in preFilter; set pick_ray=true for intersect_ray so raycasts skip non-pickable objects while other queries ignore the flag. - Populate optional rest info for blocking hits in cast_motion (point, normal, shape, collider rid/id, linear_velocity). - Update C# tests: adjust RayPickableToggle expectations and add RayPickableMixed test verifying ray-only enforcement and that point/shape queries ignore pickability.
- Add signed/mirror-capable scaling for convex polygon shapes. - Dispatch geometry scaling via _shape_geometry_scale_for so convex shapes use a signed scale path; introduce _shape_geometry_scale_signed. ConvexPolygonShape3D now cooks a point-reflected (mirrored) convex mesh and stores convex_mesh_mirrored, uses abs(scale) when creating PxConvexMeshGeometry, and updates ensure/release helpers. - Query path (_build_query_shape) uses signed scale for convex queries when transform determinant != 0. - Add GPU-note comment and C# tests verifying mirrored convex behavior (ray, rest, query). Tests added (ShapeTests.cs, shapes 75/75) - PHYSX-SHAPE-MIR-001 — dynamic convex cube with an X-mirrored shape transform drops and rests at its half-height without NaN/crash. - PHYSX-SHAPE-MIR-002 — the decisive geometry check: an asymmetric wedge hull attached mirrored; a raycast at the mirrored slope position hits at the mirrored height (0.75) with the mirrored normal (+X tilt), while an unmirrored control body confirms the base geometry. - PHYSX-SHAPE-MIR-003 — rest_info with a mirrored query shape overlapping the floor returns an up normal and a floor-surface point, agreeing with the unmirrored control (query-vs-simulation agreement).
spaces/physx_direct_space_state_3d.cpp — one new static helper wired into both sweep paths: - _sweep_hit_opposing_normal(hit, dir, out normal): for sweep hits on PxTriangleMeshGeometry/PxHeightFieldGeometry (with a valid faceIndex), it re-derives the true world-space face normal via PxMeshQuery::getTriangle + PxTriangle::normal, keeping the orientation that agrees with the reported hit normal (the side the mover approached from; the SDK itself flips for negative-determinant mesh scales). It returns false — treat as non-blocking — when the resulting normal does not oppose the sweep direction (dot ≤ −0.001, the ancestor's proven threshold). Primitive-geometry hits keep their sweep normal but still get the opposition test, since a genuine forward block must oppose the motion. - _body_motion_cast (body_test_motion): positive-distance hits now go through the helper — edge-derived or contradictory-normal hits are rejected as grazing artifacts, and recorded hits use the re-derived face normal (feeding _fill_collision_from_sweep, so CharacterBody3D slide-collision normals are face-correct). The distance ≤ 0 eMTD/initial-contact branch (REG-0012) is deliberately untouched — its MTD normal is a push-out direction, exactly what Godot consumes there. - cast_motion: the same check runs in the forward-hit branch; a rejected grazing hit reports an unobstructed motion (safe/unsafe = 1), and the rest-info normal (from Phase 2) now uses the face-corrected normal.
Articulations are now fully usable from GDScript/C#, links have real collision shapes and filtering, and the full suites pass: C# 346/346 (articulations 5/5) and GDScript 47/47 (including the new binding test).
Module changes:
objects/physx_articulation_3d.{h,cpp} — the link list became a LinkRecord vector (PhysX link, its collision shape, layer/mask, density), and four capabilities were added:
- set_link_shape(index, PhysXShape3D*, transform): detaches the old shape, creates a per-link PxShape from the shared blueprint via the established create_shape + attachShape + release refcount pattern, composed with the shape's alignment pose. It reuses the Phase-4 scale dispatch (signed scale for convex polygons — mirrors preserved; abs for everything else) and applies REG-0014 semantics (concave shapes degrade to query-only on links, which simulate). Mass properties are recomputed at the link's stored density.
- set_link_collision_layer/mask (+ getters): per-link filter data written to the shape's word0/word1, so the module's filter shader and pair-callback semantics apply to links unchanged. Default layer/mask is 1/1, matching bodies.
- get_link_velocity(index): { "linear": Vector3, "angular": Vector3 } — PxArticulationLink inherits these from PxRigidBody.
- Crash guard found by the binding test: set_drive/set_limit on the base link (no inbound joint) dereferenced a null joint pointer and crashed the engine natively. Both now fail with a clear error. The GDScript binding test caught this on its first run — exactly what that suite is for.
physx_server.{h,cpp} — six new methods (link shape, layer/mask set/get ×2, velocity) plus ClassDB bindings for the entire articulation API (all 17 methods), following the vehicle block's pattern with the same inner-server caveat documented. The add_link box default is unchanged, so existing lab code keeps working.
doc_classes/PhysXServer3D.xml — full method documentation for the articulation API (enums, axis/joint-type tables, ordering constraint that links must be added before articulation_set_space).
Tests
- ArticulationTests.cs (new category, 5 tests): a revolute ACCELERATION drive holds a 0.5 rad target on a fixed-base two-link chain (angle + twist-axis checks); layer+mask 0 makes a link non-solid (sphere falls through to the floor); set_link_shape replacement is collision-bearing (sphere rests on the new shape); link velocity matches the finite difference of the transform under free fall; layer/mask round-trip.
- GDScript bridge + binding test: physx_articulation_bridge.gd (path-loaded, no class_name so it doesn't depend on the stale global class cache) and a GDBIND-ART-001 block exercising every bound method through GDScript → ClassDB → inner server, including multi-frame simulation.
- ArticulationApi.cs mirrors VehicleApi's reflection pattern with the per-call bridge fallback — needed because the generated C# glue predates the new bindings.
GPU soft-body collision exceptions are implemented and verified working. CPU soft bodies gained reference-parity area gravity overrides, the query-invisibility limitation is now explicit.
The three sub-goals:
1. GPU-path collision exceptions — implemented. The investigation went better than the plan's fallback branch anticipated. An empirical probe (GPU PxDeformableVolume soft bodies over a control platform vs. a platform whose layer/mask kills the shader pair) returned SHADER_KILLS_DEFORMABLE: the filter shader's eKILL fully controls GPU deformable-vs-rigid pairs in this SDK. That made the registry design viable:
PxFilterData.word2 (previously reserved-unused) now carries an exception slot on shapes whose owner participates in at least one soft-body exception. Slots are allocated lazily on PhysXObject3D (bodies, areas, soft bodies alike) and are monotonic — scenes without soft-body exceptions never write word2 and never touch the registry, so the shader cost is zero there.
PhysXSoftExceptionRegistry (soft slot → set of body slots) lives in physx_filter_shader.{h,cpp} with the documented thread contract: written only between steps on the server thread, read-only during simulate() — same happens-before argument as the existing actor userData floats.
The shader kills a deformable-vs-body pair whose two slots match a registry entry (both orientations handled). soft_body_add/remove_collision_exception and the soft-body free() branch maintain the registry and push slots into the deformable's collision shape (via a new PhysXSoftVolume3D::set_exception_slot, plus the slot is baked into Params so a volume built after the exception still gets it). Bodies propagate word2 through the existing update_shapes_collision_filter. The CPU XPBD path is untouched — it already enforced exceptions via the per-vertex query exclude list.
2. Area overrides — reference behavior verified, CPU parity implemented. The reference backend (GodotSoftBody3D::predict_motion) does resolve area gravity overrides (priority-ordered, override modes, default-area fallback) — contradicting the plan's expectation. The module's CPU path now does the same via PhysXSpace3D::_resolve_soft_body_gravity (AABB overlap approximation of the reference's broadphase pairs), with the override resolver and point-gravity helper extracted from physx_body_3d.cpp into shared helpers in physx_area_3d.{h,cpp} so body and soft-body resolution can't drift. GPU deformables keep scene gravity — the solver has no per-body gravity injection; that's documented rather than worked around. Area wind on soft bodies (also in the reference) is deferred and documented.
3. Queryability — made explicit. physx_query_filter_callback.cpp now documents that actors without PhysXActorUserData (GPU deformables, articulation links, particle systems) are invisible to scene queries by design, and that soft_body_set_ray_pickable is inert — with the reason (Godot's reference resolves soft bodies through its own pair system) and what query support would require. The same limitations block is in physx_soft_body_3d.h.
- Avoids expensive mass/inertia re-computation for non-mass body parameters by splitting surface-only updates from full actor re-application. - Exposes collision pair counts via ProcessInfo and documents island count as 0 because PhysX does not expose an island counter. Added C# coverage for the new stats behavior.
- Map Godot's SPACE_PARAM_CONTACT_MAX_ALLOWED_PENETRATION into PhysX by storing a per-space half-rest offset and applying it to newly created PxShapes. - Add physx_apply_space_rest_offset (declared in physx_shaped_object_3d.h) and call it when creating/attaching shapes (PhysXShapedObject3D, PhysXArea3D). - Cache unmapped contact-related SpaceParameters so SpaceGetParam round-trips for all eight parameters. - Remove unused PhysXVehicleSceneContext stub files and references. Add C# tests for space param round-trip and penetration/rest behavior.
The mechanism (adapted from the ancestor, matched to this module's structure): with more than one fluid in a space, PhysXSpace3D::step sets a per-step deferred flag before simulate(). Each fluid's onPostSolve then kicks only the smoothing (+anisotropy) kernels on the CUDA stream and stashes the PxGpuParticleSystem/stream/count — PhysX invokes each fluid's callback in turn during the solve, so inlining the sync there made system 2's kernel wait for system 1's callback to finish blocking (N serial GPU-idle gaps). After fetchResults, a new finish_isosurface_extraction() pass runs once per fluid: the first sync absorbs the whole batch's in-flight GPU work, then the outlier clamp, extraction and foam kicks proceed per fluid. Single-fluid spaces keep the inline path byte-identical to before (flag off → same code path), and the mode flip self-heals (a pending from a batched step is dropped if the count drops to 1). Publication timing and the one-30 Hz-tick latency are unchanged — publish still happens in onPostSolve after at least one full fetchResults between kick and read, same safety margin as before. Measurement (the phase's completion criterion): my first 2-fluid probe was noise-bound (±0.3 ms run-to-run vs ~0.13 ms expected win), so I amplified to 8 fluids (~24k particles each) and A/B'd by temporarily forcing the mode off — flag-off is the honest BEFORE since the inline branch is unchanged code. Five samples each: Inline (BEFORE): 22.97 / 21.97 / 22.31 / 22.13 / 23.67 ms avg frame — median 22.31 Batched (AFTER): 21.82 / 21.64 / 21.80 / 21.29 / 21.68 — median 21.80
Optional async stepping ships flag-gated (default off), and the completion criteria are met: the extended suite passes in both modes (354/354 async off, 354/354 async on, determinism 5/5 in async mode).
Engine's actual phase order:
tick N+1: sync() → flush_queries() → scripts (_physics_process) → end_sync() → step()
sync() runs at the start of each tick, fetching the solve that step() kicked at the end of the previous one. That means with async stepping, scripts still always run after a fetch — they read the same data as before, and the solve overlaps only the tail of the tick (deferred-call flush, node destruction, rendering). The behavioral shift is much smaller than the plan feared, and the plan's threading note ("the risk is ordering, not concurrency") turned out to be accurate for this reason.
Implementations:
1. Flag + split (physx_project_settings.{h,cpp}, physx_space_3d.{h,cpp}, physx_server.{h,cpp}):
- New physics/physx_3d/simulation/async_step (default off), read live per server step so it can be toggled at runtime — this is what makes both-mode validation and mid-flight flip-safety possible.
- step(): pre-step hooks, vehicle update, isosurface batching flag, simulate() — then returns if async. sync(): fetchResults(true) + the whole post-solve pipeline, extracted into a shared _finish_step() (queued mutations → GPU readbacks/CPU soft bodies → body post-step → vehicle post-step → active-actor count). With the flag off, step fetches inline and sync() no-ops — byte-identical to the historic path. end_sync() stays a marker.
- INFO_ACTIVE_OBJECTS moved to per-space counts taken right after each fetch (in async mode it's read one phase later — equivalent semantics, documented).
- step() called with a solve in flight now fetches defensively instead of erroring (covers mid-flight flag flips).
2. In-flight window hazards (the plan's ownership mandate): the window between step and sync is real — deferred engine calls and node destruction land there (I confirmed via main.cpp: message_queue->flush() runs right after step). All actor mutations now route through the space: add_actor/remove_actor queue into a pending-ops list applied after the fetch, and a new release_actor transfers PxActor ownership so a mid-flight destroy releases the actor only after the solve that still references it completes. Six sites were routed: body _destroy_actor, area destructor, and the GPU fluid/cloth/soft-volume destroy paths (plus their addActor sites). _terminate_scene fetches before releasing the scene if the space died mid-flight.
3. Scene queries: a query against a running scene is a hard PhysX error, so every PhysicsDirectSpaceState3D entry point force-fetches a pending solve first (_ensure_space_synced). Frames whose scripts query collapse the async overlap — that's the documented cost; frames that only read body state keep it.
Validation
1. Toggle mechanism: PHYSX_ASYNC_STEP=1 env var → TestMain sets the setting at startup (verified the banner appears and the flag is live in the run).
2. Extended suite × 2: async off 354/354, async on 354/354 (3,207 assertions) — including determinism 5/5 (same-solve results unaffected by host scheduling), stress 4/4, vehicles, articulations, soft bodies, and all query categories under the lazy-sync path.
3. PHYSX-LIFE-016 (new dedicated lifecycle test): a body freed via the message queue — landing mid-flight in async mode — leaves the space simulating cleanly (canary falls); in sync mode it's harmless by construction. Uses a GDScript bridge + CallDeferred since MessageQueue isn't C#-exposed.
The contact offset for PhysX shapes was being reset when margins changed, dropping the space-mapped rest offset and leaving restOffset >= contactOffset. This commit reapplies the space rest offset after shape updates and corrects the stepping flag timing so _finish_step runs after the scene is marked idle, preventing re-entrant sync/fetch recursion during post-solve queries.
1. Map per-axis 6DOF spring equilibria into a PxD6Joint drive target (setDrivePosition) and make spring drives unlimited (forceLimit=PX_MAX_F32). 2. Refactor GPU particle-fluid isosurface extraction to clamp outlier particles (clamp_outliers) to avoid infinite mesh stretching. 3. Add PhysXServer3D.soft_body_is_gpu binding, clarify soft-body query-invisibility (reverted userData wiring), adjust query filter for soft bodies, remove an unused variable, apply style/indent fixes, 4. update README and add related C# tests.
Aligns PhysX stepping with the async/sync flow by removing the premature scene simulation call, avoids stale wheel steer-angle data before the first write, and fixes wheel attachment pose conversion from the chassis body frame to the COM frame so suspension raycasts land at the expected positions.
Fixed a bug where negative (right) steer commands produced second-quadrant wheel angles via atan2, causing the vehicle to spin. The steer math now operates on |delta|, uses atan with a clamped positive denominator, and re-applies the sign to the final target. Adds a regression test (PHYSX-VEHI-019) verifying per-wheel angles and that a right steer yaws the chassis toward +X. Changes: modules/physx/vehicles/physx_vehicle_3d.cpp, modules/physx/test/project/tests/csharp/VehicleTests.cs.
Every scene-mutation path that could run between step() and sync() in async mode is now either routed through the existing deferred-op queue or forced to fetch the in-flight solve first. All 7 edited translation units pass a full MSVC C++20 syntax check (/Zs) against the vendored PhysX SDK and the Redot core headers.
Fix A — Vehicle surface_frictions dangling material (F-02). The grip table (Vehicle2State::surface_pairs) now has a parallel surface_pair_bodies RID table recording which ground body each entry was resolved from. apply_wheel_params writes both; new PhysXVehicle3D::invalidate_surface_pairs_for_body() compacts matching entries (order-stable, per-wheel nbMaterialFrictions kept consistent); new PhysXServer3D::invalidate_vehicles_surface_pairs() walks all surviving vehicles and is called from free()'s body branch before memdelete(b) releases the body's material. Fix C — Fluid set_capacity isosurface desync (F-05). set_capacity now destroys and rebuilds the isosurface stack when it exists (same pattern as set_foam_size), so the extractor's device/host scratch always matches the new capacity that onPostSolve feeds it. Fix D — PBD spawn int overflow (F-06). spawn() now computes per-axis cell counts and their product in int64_t, per-axis clamped to particle_count; a degenerate cap clamps to 0 instead of a negative resize. Fix E — Chunk emitter (F-19). Continuous emission normalizes emission_direction once per tick (zero falls back to up, matching spawn_at), so a non-unit direction no longer silently scales launch speed. Chunks carry a stable monotonic id; ChunkBody.index → ChunkBody.id; apply_chunk_impulse resolves by id (freeing a chunk mid-call is now a no-op, never an impulse aimed at the wrong chunk). The MPM fluid coupling was updated to carry ids.
- Integrates NVIDIA Blast authoring and runtime into the PhysX module. Adds PhysXBlastAsset, PhysXBlastAuthoring (mesh fracture), and PhysXDestructible3D (runtime damage/fracture/split/spawn). - Wires Windows-only NvBlast libs/DLLs in SCsub with a `blast=no` switch to disable. Provides editor UX: fracture dialog, inspector preview, context-menu entries, gizmo, icon, and preview builder. Registers new classes and doc XML, and includes a headless smoke test (gdscript) covering authoring → resource round-trip → destructible lifecycle. - Non-Windows platforms currently build without library wiring (prints a notice).
- Hinge axis now matches godot_physics_3d joint_make_hinge (transform variant) composes a Z→X frame rotation into both joint frames: godot_physics_3d constrains hinges about the frame's local Z; PhysX revolute joints rotate about local X. I initially used +90° about Y, which maps the PhysX axis onto Godot's −Z — the test run caught it immediately (wz=-1.44 for a +4 motor target), confirming the reference's -90° constant is the sign-preserving one. joint_make_hinge_simple is untouched (its frames already put the axis on X directly). - Joint RIDs survive body free / mode changes PhysXJoint3D now references the PhysXBody3D wrappers (not raw actors) plus cached frames, and bodies keep a joint list: Freeing a body notifies body_removed() — the PxJoint is dropped (fetch-safe via the Phase 1 gate, connected dynamics woken) but the wrapper, RID, kind and full parameter cache survive; no more "RID not found" errors and no more silently deleted joints. Actor recreation (static↔dynamic set_mode, scene entry) notifies rebuild() via _add_to_scene() — a dormant joint resurrects against the new actor with its entire configuration restored. release_joints_for_actor (the destructive path) is removed from the server and header. - Parameters are cached before anything else The wrapper's cache is now the single source of truth: every setter stores first, then pushes to the live PxJoint (config applied on creation/rebuild via _apply_params()). Parameters set during a dormant/transient configuration survive to resurrection — regression-tested. Two subtle state tracks were needed for faithful rebuilds: the slider's "limit was ever configured" bit (fresh sliders must stay unlimited) and the cone-twist "limits were ever set" bit (fresh cone limits are wide, not hair-thin-locked). A side effect of the redesign: the 6DOF ENABLE_MOTOR flag now caches the X-axis (twist) velocity correctly — the old code silently skipped it.
- Captured node scale on Body transform and Area transform changes and re-bake attached shapes against that scale. - Added PhysXShapedObject3D::refresh_shape_scaling to rebuild PxGeometry and local poses (scale origin, preserve rotation), update contact offsets, wake dynamic bodies, and recompute inertia for bodies. - Adjusted shape placement when adding shapes to account for body_scale. - Added C# tests for scaled static box queries, scaled sphere rest, mirrored convex collision, and scaled area gravity override to verify behavior.
1. Recovery margin slack + 0.4-scaled passes _body_motion_recover now implements the godot_physics contract exactly, with the constants taken from godot_space_3d.cpp (margin floored at 1e-4, min_contact_depth = margin × 0.05, 0.4 × remaining MTD per pass, 4 passes): penetration shallower than the slack counts as rest separation and is left alone, and each pass applies 40% of what remains — one call recovers ~87% of a deep embedment instead of snapping the full MTD. p_margin, previously unused, is now the knob driving the slack. The MTD/collision-reporting structure is untouched. 2. Sync callbacks gated on actual activity _finish_step no longer iterates every body. The sync set is now: active actors (from the getActiveActors query that already ran for INFO_ACTIVE_OBJECTS) ∪ all kinematics (Godot fires their state callback every tick — a stopped AnimatableBody3D platform must decay its carry-velocity to zero) ∪ newly-asleep bodies (in the previous active set but no longer — one final sync delivers the resting pose). Continuously sleeping bodies cost zero callbacks; a RigidBody3D's node sync resumes when it wakes. The unregister_body hook also clears the previous-active list so freed bodies never leave dangling pointers. 3. Moving areas stop waking the world The per-move bounds-read + overlap-query wake pass in PhysXArea3D::set_transform is now gated on the area actually imposing something (has_gravity_override() || has_linear_damp_override() || has_angular_damp_override()). Pure monitoring areas stop paying a query per move and stop keeping sleeping stacks awake. Wind is unaffected by design — sleeping bodies receive it from their own pre-step, and the trigger pairs driving overlap lists are re-evaluated by the kinematic move itself.
- Correct the per-vertex mass scaling for point impulses so soft-body pushes match the solver's even mass distribution. - Also fix contact-plane generation for sphere collisions by using the actual surface point/normal contract, preventing resting vertices from hovering and sticking in deep overlaps.
- Cloth solver - scale cached rest_volume by p_scale^3 to keep volume constraint consistent and distribute damping per substep to avoid compounding; - GPU particle fluid - increment mesh_version when clearing to avoid stale surfaces; soft volume - update PxShape simulation/query filter data when params change so deformables respect runtime layers/masks/exceptions; custom geometry callbacks - support per-scale callback instances, refresh params and proper cleanup; cone/cylinder adapt to new API; - Heightmap - use full signed 16-bit quantization range and add Y offset to center heights, reducing quantization artifacts.
- Tighten PhysX build gating to supported platforms only, - Fixes default gravity initialization to honor project settings, and avoids redundant actor flag writes in the pre-step path. - Cache repeated StringName lookups for gravity and async stepping, updates soft-body filter comments, and corrects the heightfield scale floor to prevent invalid raycast magnitudes on flat terrains. - These changes reduce unnecessary runtime churn and fix mismatches between project gravity settings and PhysX behavior.
With rendering/driver/threads/thread_model = RENDER_SEPARATE_THREAD, the RenderingDevice's render-thread guard failed every main-thread call the Flow path made -- no gas rendered at all. FlowSimulation::step now gathers the frame's inputs (emitters, colliders, settings) on the calling thread and posts the grid work to the render thread; configure/reset/destroy post the same way and sync so availability and teardown ordering hold. The readback decode runs in the posted step and decoded frames queue for poll_readback under step_mutex. PhysXFlowRenderEffect guards its flows list while a frame draws it and frees the shared effect on the render thread (posted unref + sync), so teardown cannot free RenderingDevice resources from the wrong thread. PhysXParticleFluid3D on MPM counts as spawned once configured -- with a separate render thread the build hasn't run when spawn() returns. The module's direct-touch server APIs (fluid setter family, cloth setter family, free_rid) now check can_touch_scene(): outside physics process on the main thread they queue through a dispatch body carried by PhysicsServer3DWrapMT's command queue (run_on_physics_thread()), matching how stock PhysicsServer3D calls are marshaled -- no more main-thread stalls against the physics step, and scene mutations land in order on the physics thread. Our soft body builds are node-notification driven (main thread only), so the reference's off-main-thread mesh-read deferral was verified not applicable.
…rs on mesh terrain PhysXSpace3D: get_moved_bodies() exposes the bodies PhysX reported moving in the last completed step (the eENABLE_ACTIVE_ACTORS snapshot _finish_step maintains for sync gating) — physics interpolation / streaming consumers walk this instead of diffing every body's transform. body_test_motion cast phase: capsule/sphere sweeps against triangle meshes and height fields now report the true contact normal — from the touch point to the nearest point of the mover's core — instead of the triangle face normal, which made a low lip's vertical face a wall and a tall step's top a floor. The core normal tilts with the lip height, so a default capsule walks up to ~15 cm lips and stops from ~20 cm steps, like Godot Physics and Jolt. A round mover that starts the sweep pressed into mesh/heightfield terrain is pushed out along the true normal and the motion re-swept from there when the push opposes the horizontal movement (the recover phase cannot depenetrate those geometries; the old stuck verdict froze pressed characters). Floor-like press-outs report without clamping travel; motion-opposing ones stop the travel so a body cannot ratchet itself up a step. The stuck path now copies its deepest-overlap hit by value — the stored pointer lived in the shared sweep scratch and read stale data after the next shape's sweep.
…ture, VS 2026 SDK builds - Flow readback: the CPU sparse decode now reads blocks straight from the sparse table -- each block's location from (blockIdx << 2) + locationOffset, its packed atlas base from blockIdx + blockLevelOffsetGlobal -- the same lookups the GPU-side rendering does (NvFlowShader.hlsli), instead of pairing against the exported location list whose order is not the table's. Block dims are per-axis (Flow blocks are anisotropic; the old blockDimBits.x collapse over-allocated the dense volume in Y/Z and over-read neighboring atlas rows), and the dense volume anchors at the layer's worldMin. The decoded gas now sits where the simulation put it, so sample queries and the floor test's physical reads mean something. - PhysXFlowSimulation3D: sample_smoke() and sample_temperature() read a world position from the last CPU readback (-1.0 without one, 0.0 where no gas is). - Flow emitters: localToWorldVelocity now carries the basis only -- the old full transform added the emitter's world position to every injected velocity (an emitter at y=4 with local velocity -3 injected +1 up). - Flow floor test: the physical half deferred to the mapping fix is in -- an emitter straddling the floor line fills the cells below it with the floor off and reads ~0 there with it on (PHYSX-FLOW-002). - SDK builds: bundled patch 0004 generates the vc17 PhysX presets with the VS 2026 generator and the v143 toolset when VS 2022 isn't installed (Flow's build.bat uses the v143 toolset folder too), so CUDA 12.x keeps working; the Flow CI Windows job applies it (no-op on VS 2022 runners).
Adds the physics/physx_3d/simulation/gpu_dynamics entry to the project settings reference and moves sample_smoke/sample_temperature into alphabetical order in PhysXFlowSimulation3D.xml, matching doctool output for the CPU-readback commit.
Stability: custom-shape wrappers detach owners in the destructor body before the inner shape's callbacks die; custom-geometry callback instances are reference-counted off the owners' live PxShapes and freed when unreferenced (animated scale no longer leaks); vehicle nodes ensure_synced before touching the scene in _build/_destroy (no add/remove/release during an async solve); RayCast3D hit_from_inside resets multi_hit before the raycast and runs the origin overlap without the post-filter (a RayCast3D with hit_from_inside detects again). Correctness: body/area set_shape preserves the other slots' order; gas density slices update every tick and the point cloud uses the row-major MultiMesh transform layout; water spray caches authored emitter values across rebuilds; skinned cloth frees buf_boxes and the GPU path honors layer/exceptions/live stiffness; vehicle server caches engine-drive params before the v2 guard; vehicle exceptions re-stamp rebuilt chassis shapes and keep pre-build adds; MPM fluid collider ids reach the solver so reaction impulses route (one-way coupling fixed); the flow bridge passes its lifetime; flow readback slots stamp only when copies were recorded, emitter luids survive pruning, the scatter pass fills shadow entries, and the temp colormap GPU probe is gone; the editor runtime-install failure stays visible and retries clear the stale download target. Tests: C# --list writes its JSON report (nightly per-category listing works; an empty suite run fails instead of passing), GDScript test IDs de-conflicted from the C# suites (PHYSX-JMW/LIFEC/BLAST-004), t_articulation builds once in a real space and simulates, the flow demo drone's collider paths resolve, and the GPU CI tier writes reports to its own directory.
…raries
Flow's premake build emits GNU_PROPERTY_X86_ISA_1_NEEDED = x86-64-v3
(AVX2) for libnvflow.so / libnvflowext.so, but a full disassembly of both
shows no AVX instruction at all -- the requirement comes from the build
flags, not the code. glibc 2.41+ validates that note at exec and dlopen
and refuses the libraries on pre-v3 CPUs ("CPU ISA level is lower than
required"), which locked engines out of x86-64-v2 machines (reported on
a Celeron N4500) even though every instruction in them is baseline-safe.
misc/fix_elf_isa_note.py rewrites the ISA bitmap of the note in place;
the vendored Linux Flow pair is demoted to baseline x86-64, and the Flow
CI workflow runs the same tool on freshly built libraries before staging.
The static-checks workflow rejected the new ISA-note tool (unused import, slice formatting). Ran the repo's pinned ruff 0.12.0 check --fix and format; also fixed the tool's summary message to distinguish "already at level" from "property not found". Verified idempotent against the vendored Flow libraries and the full suite stays green.
glibc 2.41+ refuses ELF objects whose GNU_PROPERTY_X86_ISA_1_NEEDED note
exceeds the CPU at exec and dlopen ("CPU ISA level is lower than
required"). The new always-on step prints the runner's toolchain identity,
the compiler's pinless default ISA macros, and the declared ISA level of
every produced object in bin/ (engine binary + staged PhysXGpu/Blast/Flow
libraries), so a portability regression in the Linux artifacts is named
directly in the CI log.
Brings in the latest 14 upstream commits: the .NET 10 / C# 14 upgrade, bit-exact x86 SIMD fast paths, GDScript trait fixes, the RefCounted unreference race fix, CONNECT_NO_EDITOR, WorldScape3D fixes, renderer cleanups (unused includes, dead GLES3 calls, XR texture storage, spirv-reflect build location), and the CI dotnet 8 -> 10 bump. Resolved a SConstruct conflict between the Linux -march=x86-64 ISA pin (PhysX branch) and the new simd.h SSE4.1 comment (master); both kept.
… mesh terrain - body_test_motion's cast phase derived the press-out contact for a capsule or sphere that starts overlapping a triangle mesh or height field from PhysX's initial-overlap result — an axis-pick push whose position can be a vertex a metre away. A character walking across the 3.5 cm seam between two floor pieces was pushed out along that axis by the overlap depth and snapped ~30 cm sideways. - _round_mesh_contact() now resolves the real contact from the overlapped triangles themselves: PxMeshQuery::findOverlapTriangleMesh/findOverlapHeightField (collecting up to 64) plus the closest pair between the mover's core and those triangles — normal from the triangle point to the core, depth = radius minus that distance, contact point on the triangle. A core crossing a triangle falls back to the sweep's own MTD fields, and so does a touch whose overlap query finds no triangle (disregarded). _round_sweep_contact_normal keeps serving forward hits, where the sweep's touch position is real. The rest-slack gate folds away for the mesh path: slack-level overlaps resolve through the same triangle-accurate contact, which is what makes the fix reach resting characters; one-sided meshes keep their skip, now on the accurate normal. - Lips/steps classification (floor-like reports without clamping, wall/step-face press-outs stopping the travel) is unchanged — the branches now run on the triangle-accurate inputs.
…n walk - Each PhysXFlowSimulation3D looked its lights up every ~60 steps with find_children() over the whole scene: one walk for the directional light and, with light scattering, one each for omni and spot lights. In a scene of 200k nodes that was a 225 ms frame once a second, per flow. - The module now keeps the scene's Light3Ds in one index shared by all flows: one find_children sweep when the first flow enters the tree, then kept current from the SceneTree's node_added / node_removed, torn down when the last flow leaves. Class-partitioned id vectors (directional / omni / spot) take each lookup straight to its candidates; the directional pick takes the visible light with the smallest tree order — the node find_children listed first — and the scattering gather keeps the omni-then-spot order, both scoped to the caller's root by is_ancestor_of. Root chains, the ~60-step re-pick cadence and the cached light id are unchanged. - Unlike the upstream reference's unsynchronized static set, the index is mutex-guarded: in this module the directional search runs on the step thread and the scattering search on the render thread while the tree signals fire on the main thread. Lookups copy the id list out under the lock and touch Nodes outside it, and no other module lock is held across an index access.
- A body freed between two steps still gets its pairs' touch-lost report in the next step, with the pair header flagged eREMOVED_ACTOR_0/1. The callback skipped such pairs whole — crash-safe, but it dropped the surviving body's final contact report and the debug-contact recording for them, and it was one dereference away from the freed actor: the velocity pre-resolve called is<PxRigidDynamic>() on the dangling actor pointer before any flag check. - The removed side now resolves to no body and no data instead of ending the header: its userData and dynamic-actor lookups are skipped (the SDK contract forbids dereferencing a removed actor), its shape pairs are skipped below as removed shapes as before, and the surviving side keeps its report — with an empty collider identity for the freed side, filled by the newly guarded _fill_body_contact. A live side without userData still resolves to nothing, as before; a removed side is the only legitimate null.
The render effect drew once into the whole frame with the combined camera's projection, so under stereo the eyes didn't get their own gas. Each view now draws on its own slice of the color and depth with its own projection: the effect loops view_count, fetching per-layer textures and per-eye projections and eye offsets from the render buffers and scene data this module's renderer already provides — no renderer changes needed. The camera transform stays shared across the eyes (each eye's offset rides in its projection), the back-to-front flow sort stays per-frame at the head origin, and the single-view path keeps the whole-texture getters byte-for-byte, preserving reflection-probe override-depth handling. Flow's offscreen colorize pass (colormap + shadowing) is view-independent: FlowSimulation::render gains a lighting-pass flag so under XR only the first view colorizes and the rest raymarch the same colorized volume. The per-view scatter pass and copy-back are unchanged — both are genuinely view-dependent.
- Water: clear cell_fade in _rebuild(), push water_level to the material, rebind uniforms/textures on material replacement, zero GPU ripple state on recenter(), guard the wake solver destructor at shutdown - Particles: recreate MPM isosurface buffers after reconfigure, rebuild the MPM backend on render-mode changes, match _shown_solver to _resolved_solver - Flow: free the per-node FlowScatterRD on the render thread, stop rotating emitter velocities before Flow rotates them again - Shapes: count inner-shape callback references through the wrapper's owners and park replaced inner shapes until unreferenced; real pointer checks on the cooking paths - Queries: one-sided-mesh checks in the motion phases read the hit shape's blueprint, not the mover's - Editor: gate required runtime lists on build flags, remove only files the failed install wrote, reject unsafe bundle names - Vehicles/Server: reseed tire rest load after space adoption, capture the authored wheel hardpoint once, wait for cloth_set_space before cloth_build
…st harness cleanup - fix_elf_isa_note.py parsed the property datasz as the ISA bitmap and corrupted the notes; now parses/patches correctly, verifies, and repaired the vendored Flow libs to well-formed baseline notes - wake solver: null gpu guard in build() (headless crash) - tank: null PhysXServer3D check in _ensure_exception_synced() - MPM shader: standard Jacobi sign for equal diagonals - Flow: DIAG_SPARSE_LAYOUT was unreachable after return - README: duplicate heading + gpu_dynamics wording - test harness: untrack deliberately freed RIDs ( silenced 1143 "attempted to free unknown RID" errors per C# run)
Its restore needs the repo-built Redot.NET.Sdk packages, which live in the gitignored modules/mono/nuget and never exist on the static-checks runner — the hook aborted with "Restore operation failed". The test C# sources stay compile-checked by the mono test job.
- release.yml: the Linux editor no longer ships the PhysX/Blast/Flow .so files — patchelf drops the binary's NEEDED entries (all imports are lazily-bound functions), the libraries are split into a physx-runtime-linux.zip artifact for the runtime repository, and the release zips are built in-workflow so the executable bit survives - Blast: new physx_blast_runtime loader + gates in destructible and authoring paths — with the runtime absent, Blast is disabled with clear errors instead of "symbol lookup error" crashes; installs take effect without a restart - The editor plugin's project-open / engine-switch download prompt, manual-install fallback, and restart flow are unchanged and now apply to Linux as well
…estructibles - Flow: bind mesh-collider buffers after final storage (dangling-pointer UAF), clear desc_active/mapped_desc on grid destroy, document that flow/device_api applies only outside the RenderingDevice path - Joints: rebuild the swing pyramid when ENABLE_ANGULAR_LIMIT turns on - Particle fluid: MPM source registry keeps ObjectIDs, not raw pointers — lagged impulses skip freed sources instead of reading freed memory - Cloth: rest_volume scales by the relative factor, not the compounded absolute scale - Destructibles: validate .chunks sidecar counts and unwind the family on sidecar failures, skip body-less anchored islands in transform sync and debris aging, and retire the intact placeholder by tracked identity with spawned counted from real spawns
# Conflicts: # doc/classes/CompositorEffect.xml
…time edges - fix_elf_isa_note.py: ISA_1_NEEDED is 0xC0008002 per glibc elf.h (2.41+), not 0xC0000002 (that is the CET FEATURE_1_AND marker) — the corrected tool now finds and demotes the real x86-64-v3 requirement the ubuntu-latest toolchain stamped into the released Linux editors, the actual cause of the "CPU ISA level is lower than required" crashes; also reads PT_GNU_PROPERTY when section headers are absent - release.yml: pin Linux release builds to ubuntu-24.04 (glibc 2.39, baseline x86-64 toolchain, matching linux_builds.yml) and run the ISA demote on the shipped binaries - destructibles: island-owner piece exempt from debris lifecycle, chunk sidecar rejects zero chunks, _spawn_intact skips body setup when the intact piece failed to spawn - motorcycle: null PhysXServer3D guard in _ensure_exception_synced - MPM: clear `built` when a reconfigure fails so freed RIDs are never dispatched - export plugin: look up export templates under REDOT_VERSION_FULL_CONFIG - README: keep the run_on_separate_thread setting name in one code span
The release job has no repository checkout, so its ISA-demote step could not find fix_elf_isa_note.py and aborted the whole release (exit 2). The demote now runs in the four Linux build jobs right after their builds; the release job only patchelfs, splits the runtime bundle and packages the assets.
…es and vehicles - Vehicles: fetch the in-flight solve in _rebuild() before destroying vehicle constraints (async stepping), validate every anti-roll wheel id before any cache/state write, and guard the exception paths against a null PhysX server on non-PhysX backends - Vehicles: stamp every chassis shape (8-wheel tanks carry 9; the fixed 8-slot buffer silently skipped the chassis contacts) and use Math::abs in set_gear_final_ratio so the C int overload can't truncate - Flow: attach_world() refcounts per node — attach only on a world change so compositor restore/cleanup actually runs, and insert plain Node3D emitters into active_emitter_ids so their luid stops churning per tick - Cloth: skinned-cloth box colliders now pass the world-space center with an orthonormalized basis and scale folded into the half extents (the affine-inverse origin double-applied the rotation, mispositioning every box away from the world origin) - Areas: emit area-vs-area exit events from both perspectives (the other area's area_exited never fired) and fetch the solve before transform writes; articulation _destroy() removes via the articulation's own scene pointer so zero-link teardown stops erroring - Soft bodies: sync both spaces before the exception-registry writes, and guard the soft-volume host<->device accesses (pins, impulses, attachments, apply_params) against an in-flight solve - Shapes/Nodes: validate heightmap set_data into locals before storing (stale dimensions indexed past the array), and size the gas debug point-cloud buffer to the allocated instance count (any smaller buffer was rejected by the RS, so it never rendered) - Registration: PhysXFlowRenderEffect moves to the scene level so export builds get get_world_compositor, not just the editor - Misc: fix the ISA note tool's inverted segment-fallback condition (loader-visible notes were skipped when sections existed without the property section, and double-processed otherwise) and use data.find so a malformed string table no longer raises ValueError
…adback and vehicles - Soft bodies: dropping the GPU volume in set_space(nullptr) / notify_space_destroyed() now also clears mesh_ready, visual_vertex_count and the visual->physics mapping -- the GPU path never builds the CPU solver, so the stale mapping sent CPU-fallback position/impulse reads past the end of its (empty) arrays; set_space rebuilds from a valid mesh again (gate on mesh, not mesh_ready) - Flow: scatter-light gathering moves from the render callback to the node's step path -- under the Separate thread model Light3D::is_visible_in_tree() fails its read guard on the render thread and every light was skipped, silently disabling scattering; the callback now only consumes the snapshot published under scatter_mutex - Flow: poll_readback returns the newest decoded frame (per its header contract) instead of the oldest queued entry and drains the queue; _rt_reset and _destroy_grid_now clear decoded_frames so pre-reset frames cannot survive a grid rebuild - Bodies: gate setKinematicTarget on body_added_to_scene instead of space presence -- a body with a space but no shapes yet has no scene, and the rejected call lost the requested pose (the body entered the scene at its stale pose once shapes arrived) - Vehicles: refresh the wheel's authored hardpoint capture on every editor ENTER_TREE and skip the EXIT_TREE restore there -- no parent simulates in the editor, so reparenting or undoing a deletion used to snap the wheel back to its scene-open pose and silently discard edits - Soft bodies: point the attach_point_to_body warning and the header docs at the real PhysXServer3D.soft_body_set_solver_mode API (the "physx_soft_mode" metadata hook could never fire) - ISA note tool: exit non-zero when a patch did not happen (non-ELF64 input or verification mismatch) so release steps fail loudly instead of shipping an object that still carries the high ISA requirement, and catch struct/Unicode/OSError errors per file so one malformed ELF no longer aborts a --report directory scan - CI: add the missing default="false" to CompositorEffect.xml's needs_albedo member -- --doctool regenerates it from the ClassDB default and the doc check diffed (fixes the failing Linux editor job)
- Lifecycle (C#): keep the deferred-free bridge alive until the queued free_rid runs (freeing it same-frame made the flush skip the call, so PHYSX-LIFE-016 passed without ever testing the mid-flight deferred free) and Forget the victim so Dispose() doesn't free it twice - Lifecycle (GDScript): record the PHYSX-LIFEC-003 pass only when no iteration already failed -- the unconditional pass replaced timeout failures in the results map, reporting a broken async free path as PASS - Suite driver: pass --max-seconds=<driver timeout - 60s> to the engine so the C# runner's wall-clock cap matches the driver budget (fast tier killed the process before the final report landed -> CRASH; nightly aborted tests 30 minutes into a 6-hour budget) - C# tests: skip (not fail) the GPU cloth layer-matrix test when CUDA is unavailable -- cloth_create() returns an invalid RID there and the blocking suite treated missing hardware as a failure - Test project: physx_ci.csproj targets net10.0 to match the engine's RedotSharp assemblies (the stale net8.0 reference broke the C# build with NU1202 once the freshly generated nupkg was pushed)
- build-windows-editor-mono now moves the delay-loaded PhysX/Blast/Flow runtime DLLs out of bin/ into a physx-runtime-windows-mono.zip artifact, like the regular Windows build — redot-windows-mono.zip collapses from ~200 MB to the editor plus GodotSharp, and the physx editor plugin's download-and-restart flow covers the mono editor the same way: opening a PhysX project (or selecting the engine in a new one) offers the runtime download next to the executable and restarts afterwards. - The Linux mono editor was already covered by the release job's strip loop (e0a20b4); verified locally that the MSVC mono editor starts without any runtime DLL, takes the CPU fallback, and offers the download with all 12 files listed.
…ed rigs stop contracting - Interpolation smoothing: the solver ran in world space, so a cloth on an interpolated character reacted to per-tick world anchors while the render twin moved smoothly between them -- the hanging corner jittered. The solver now runs in the source mesh's local space (the render twin's): captured skeleton-local poses are placed through the skeleton-relative transform, colliders ride the same mapping, gravity is rotated and scaled into local units, and the outputs land in that space directly (identity out_xform). The twin's interpolated transform now carries the world motion, and the cloth rides it with the body. - Scaled rigs: the build overwrote the shader's rest positions with the garment as WORN, but the shader's skin() applies the full skin matrices to that buffer -- every animated target was double-skinned, with the node scale sandwiched inside the bone chain, and max distances were consumed in the wrong units. rest stays in bind space for skinning (as the reference does); the worn positions feed only the constraint and tether rest lengths, and each particle's max distance is converted to solver units by its worn blend's scale (unscaled rigs map 1:1). - Vehicle lateral sign: vehicle_direction_test pins the curved-drive contract -- positive steer curves toward +X (dx=+0.91 measured); the server already honored it.
The static checks' clang-format hook rejected the hand-wrapped continuation of the worn_scale expression in PhysXSkinnedCloth3D:: _try_build. The sum is split into its own worn_span statement, which reads cleaner and formats canonically under the pinned clang-format 20.1.0 (verified with --dry-run --Werror on the cpp and header, and the GLSL style file on the shader).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This module adds a new 3D physics backend with NVIDIA PhysX 5.12 behind Redot's standard PhysicsServer3D interface, bringing GPU-accelerated rigid body dynamics (CUDA) to Windows and Linux platforms along with a suite of high-fidelity physics subsystems, such as volumetric fluids, fracturing/destruction, advanced vehicles, and interactive water. It is implemented as a standard engine module (modules/physx) with a script-facing PhysXServer3D singleton, ready-to-use Node3D-based node wrappers, editor integration (gizmos, configuration warnings), full class documentation, and a regression test suite. The module has passed CI and builds on Windows (MSVC & MinGW) and Linux (CPU and CUDA GPU physics), with per-platform SDK libraries vendored and staged automatically. MACOS & Android are currently not yet supported.
is_gpu_dynamics_enabled(),get_gpu_device_name()).PhysXFlowSimulation3D/PhysXFlowEmitter3D/PhysXFlowCollider3Dfor real-time smoke, fire, and gas on the RenderingDevice backend, with preset colormaps, light scattering/self-shadowing, solid-collider coupling, per-sim stats, and Windows + Linux support.PhysXDestructible3D,PhysXBlastAsset,PhysXBlastAuthoringfor runtime fracturing, plus a Flow↔Blast bridge (PhysXFlowBlastBridge3D) that ties destruction debris into volumetric effects.PhysXVehicle3D/PhysXVehicleWheel3D(4W wheeled solver with Ackermann steering), plus ready-madePhysXMotorcycle3D,PhysXTank3D, and PhysXBoat3D variants.PhysXWaterSurface3Dwith caustics, boat/ship wakes (PhysXWaterWake3D), spray (PhysXWaterSpray3D), and genericPhysXBuoyancy3D.PhysXGranular3D), gas (PhysXGas3D+ emitters), particle fluids, soft bodies/volumes, and articulations - each with documented nodes and parameters.