nx_torque.nx source
↩ module page · 142 lines · 5490 B
1// nx_torque.nx -- 3D torque vector primitive (rotational analog of
2// nx_force). Layer 1 physics per [[feedback-engineering-sciences-bits-up-3d-print-first]].
3//
4// Key composition demo: nx_torque_from_position_force computes
5// τ = r × F via nx_force_cross + bit-cast. This is the FIRST
6// substrate primitive that BUILDS a different-type primitive from
7// COMPOSING another -- demonstrating the cross-primitive
8// composition pattern the engineering sciences arc relies on.
9//
10// Unit convention: Q14 Newton-metres (N·m). 1 Q14 unit = 1/16384 N·m
11// = 61 µN·m. Range ±5e14 N·m -- vastly exceeds any engineering use
12// (NEMA-23 stepper holding torque ~1 N·m; jet engine thrust ~10⁶ N·m).
13//
14// IMPORTANT unit-pairing convention for the cross product:
15// position r in metres (Q14)
16// force F in Newtons (Q14)
17// result τ in N·m (Q14)
18//
19// Slicer/printer callers operate in mm by default. Convert
20// position from Q14 mm to Q14 m by dividing by 1000 (integer
21// division loses sub-µm precision, acceptable for motor-sizing
22// calcs). Or use millimetric variant queued for v2.
23//
24// Composes:
25// nx_force (force vector input + reuses cross product)
26// nx_isqrt (magnitude via Q28 sum-of-squares)
27// nx_i128 /
28// nx_muldiv_i64 (overflow protection for large-scale ops)
29//
30// Future v2 enhancements:
31// nx_torque_from_position_force_mm (mm-position variant for slicer)
32// nx_torque_required_for_angular_accel (τ = Iα, needs nx_inertia)
33// nx_torque_at_lever (planar 2D case for simple analyses)
34//
35// license_tier: ORIGINAL
36
37import "nx_syscalls.nx"
38import "nx_isqrt.nx"
39import "nx_i128.nx"
40import "nx_force.nx"
41
42const NX_TORQUE_Q14: i64 = 16384
43
44// ===== verdicts ====================================================
45
46const NX_TORQUE_OK: i64 = 0
47const NX_TORQUE_ERR_NULL_INPUT: i64 = 1
48const NX_TORQUE_ERR_NULL_FORCE: i64 = 2
49
50func nx_torque_verdict_name(v: i64) -> *u8 {
51 if v == NX_TORQUE_OK { return "OK" }
52 if v == NX_TORQUE_ERR_NULL_INPUT { return "NULL_INPUT" }
53 if v == NX_TORQUE_ERR_NULL_FORCE { return "NULL_FORCE" }
54 return "UNKNOWN"
55}
56
57// ===== struct ======================================================
58
59struct NxTorque {
60 tx_q14: i64,
61 ty_q14: i64,
62 tz_q14: i64,
63}
64
65const NX_TORQUE_BYTES: i64 = 24
66
67// ===== constructors ================================================
68
69func nx_torque_new(tx_q14: i64, ty_q14: i64, tz_q14: i64) -> *NxTorque {
70 let t: *NxTorque = (sys_mmap(NX_TORQUE_BYTES)) as *NxTorque
71 t.tx_q14 = tx_q14
72 t.ty_q14 = ty_q14
73 t.tz_q14 = tz_q14
74 return t
75}
76
77func nx_torque_zero() -> *NxTorque {
78 return nx_torque_new(0, 0, 0)
79}
80
81// ===== cross-primitive composition: τ = r × F ======================
82//
83// Given a position vector r (Q14 metres) and force F (NxForce, Q14
84// Newtons), returns τ = r × F (NxTorque, Q14 N·m). This is THE
85// composition demo for the engineering-sciences arc -- one Layer 1
86// physics primitive (nx_torque) builds itself from another Layer 1
87// physics primitive (nx_force).
88//
89// Algebra (each component is r_i * F_j - r_j * F_i, the standard
90// cross-product formula; reused via the existing nx_force_cross
91// implementation by constructing a temporary NxForce holding the
92// position vector):
93
94func nx_torque_from_position_force(rx_q14: i64, ry_q14: i64, rz_q14: i64,
95 f: *NxForce) -> *NxTorque {
96 // Reuse nx_force_cross by treating position as a "force" vector
97 // (same Q14 3D shape). The math is identical regardless of the
98 // physical units of the operands -- they cancel through to N·m.
99 let r_as_force: *NxForce = nx_force_new(rx_q14, ry_q14, rz_q14)
100 let cross_as_force: *NxForce = nx_force_cross(r_as_force, f)
101 // Bit-cast the result to NxTorque (same memory layout).
102 return nx_torque_new(cross_as_force.fx_q14,
103 cross_as_force.fy_q14,
104 cross_as_force.fz_q14)
105}
106
107// NOTE on a NishiLang gotcha discovered during nx_torque smoke:
108// `expr\n - expr` is parsed as STATEMENT BREAK + standalone `- expr`,
109// not as continuation of subtraction. Caused nx_force_cross to
110// silently drop second term. All subtractions must fit on one line
111// or use named temporaries (the pattern used in nx_force_cross post-
112// fix). Same applies to other multi-line binary operations.
113
114// ===== arithmetic ==================================================
115
116func nx_torque_add(a: *NxTorque, b: *NxTorque) -> *NxTorque {
117 return nx_torque_new(a.tx_q14 + b.tx_q14,
118 a.ty_q14 + b.ty_q14,
119 a.tz_q14 + b.tz_q14)
120}
121
122func nx_torque_sub(a: *NxTorque, b: *NxTorque) -> *NxTorque {
123 return nx_torque_new(a.tx_q14 - b.tx_q14,
124 a.ty_q14 - b.ty_q14,
125 a.tz_q14 - b.tz_q14)
126}
127
128func nx_torque_scale(t: *NxTorque, scalar_q14: i64) -> *NxTorque {
129 return nx_torque_new(nx_muldiv_i64(t.tx_q14, scalar_q14, NX_TORQUE_Q14),
130 nx_muldiv_i64(t.ty_q14, scalar_q14, NX_TORQUE_Q14),
131 nx_muldiv_i64(t.tz_q14, scalar_q14, NX_TORQUE_Q14))
132}
133
134// ===== magnitude ===================================================
135
136func nx_torque_magnitude_q14(t: *NxTorque) -> i64 {
137 let sq_x: i64 = t.tx_q14 * t.tx_q14
138 let sq_y: i64 = t.ty_q14 * t.ty_q14
139 let sq_z: i64 = t.tz_q14 * t.tz_q14
140 let lsq: i64 = sq_x + sq_y + sq_z
141 return nx_isqrt(lsq)
142}