code wiki / (root) / nx_torque.nx

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}