acron.interop

Godot(Y-up) <-> Acron(Z-up) conventions – the single source of truth.

Godot is Y-up, right-handed; Acron is Z-up, right-handed. acron-gltf already converts loaded glTF geometry Y-up -> Z-up on load, so models arrive in Acron space and must NOT be rotated again. What still needs converting is every literal vector or transform copied straight out of the GDScript / .tscn (spawn points, Vector3.UP, gravity, hand-typed directions).

Two conventions are supported, matching the two porting styles our ports used:

1. Direct swap (the TPS style). Godot (x, y, z) -> Acron (x, z, y). The swap is an involution (its own inverse) – but it has determinant -1: it is a REFLECTION, valid only for direction/position vectors (gravity, Vector3.UP, velocities, offsets), never as a rotation-matrix basis. For full transforms use acron.transform, which handles the basis correctly.

2. Y-up logic frame (the racing style). For dense ported math (vehicle dynamics) keep the equations verbatim in a Godot-style Y-up frame: a child node with set_hpr(0, 90, 0) so local +Y is world +Z (attach_logic_frame()), converting only at the solver/render boundary. The frame mapping is the PROPER ROTATION (x, y, z) -> (x, -z, y) (g2p_rot()). Chirality trap: for world data (track collision, markers) shared with such a frame you must use the rotation convention, not the reflection swap – mixing them mirrors the world relative to the logic frame (the symptom is geometry that only matches through an impossible mirror). Use the reflection swap only for symmetric vectors (gravity, up).

For transforming vectors through a node’s frame (Godot xform/xform_inv) call NodePath.get_relative_vector / get_relative_point directly.

Global Functions

acron_to_godot_vec(v) → tuple

Inverse of godot_to_acron_vec() – Acron (x, y, z) -> Godot tuple.

apply_vertical_fov(lens, vfov_degrees: float, eps: float = 0.01) → bool

Set a lens’s field of view from the AUTHORED, VERTICAL angle.

Godot (and most engines’ camera authoring) states fov vertically; Acron’s Lens.set_fov(x) sets the HORIZONTAL angle and derives the vertical from the aspect ratio. Handing it the vertical number renders the same scene through a much longer lens — which reads as a subtly wrong camera rather than a bug, and quietly changes how much world is on screen and what a frame costs.

Converts through the lens’s own aspect ratio and sets only the horizontal angle, so a window resize re-derives the vertical — and calling this every frame is what brings it back round to correct after one. Returns True when the lens was actually written (skipped inside eps degrees, so a per-frame caller costs one comparison).

attach_logic_frame(parent, name: str = 'logic_frame')

Attach a Godot-style Y-up child frame under parent.

Inside the returned node, ported math can treat +Y as up exactly as the GDScript does; the node’s hpr(0, 90, 0) maps local +Y to the parent’s +Z. Verify empirically with render.get_relative_vector(frame, (0, 1, 0)).

g2p(v) → LVector3f

Vector from the Godot Y-up frame to the Acron Z-up frame (axis swap).

Accepts anything indexable [0]/[1]/[2]. The swap is an involution, so p2g() is the same operation under a name that documents direction.

g2p_rot(v) → LVector3f

Godot/logic-frame vector -> Acron world via the PROPER ROTATION.

(x, y, z) -> (x, -z, y) – the mapping performed by a attach_logic_frame() node (hpr 0,90,0). Unlike g2p() this preserves chirality, so it is the correct convention for world data (collision meshes, markers) used alongside a logic frame.

godot_to_acron_vec(x: float, y: float, z: float) → LVector3f

Convert a literal Godot Vector3(x, y, z) to a Acron LVector3.

Godot Y (up) becomes Acron Z (up); Godot Z becomes Acron Y. Positions use the same swap (build an LPoint3 from the result).

gravity_vec(magnitude: float = 9.8) → LVector3f

Acron-space gravity for a Godot default_gravity of magnitude.

p2g(v) → LVector3f

Vector from the Acron Z-up frame to the Godot Y-up frame (axis swap).

p2g_rot(v) → LVector3f

Inverse of g2p_rot(): Acron (x, y, z) -> (x, z, -y).