| 1 | /* |
| 2 | * LegacyClonk |
| 3 | * |
| 4 | * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design) |
| 5 | * Copyright (c) 2017-2021, The LegacyClonk Team and contributors |
| 6 | * |
| 7 | * Distributed under the terms of the ISC license; see accompanying file |
| 8 | * "COPYING" for details. |
| 9 | * |
| 10 | * "Clonk" is a registered trademark of Matthes Bender, used with permission. |
| 11 | * See accompanying file "TRADEMARK" for details. |
| 12 | * |
| 13 | * To redistribute this file separately, substitute the full license texts |
| 14 | * for the above references. |
| 15 | */ |
| 16 | |
| 17 | /* Fixed point math extracted from ALLEGRO by Shawn Hargreaves */ |
| 18 | |
| 19 | /* The Clonk engine uses fixed point math for exact object positions. |
| 20 | This is rather silly. Nowadays we should simply use floats. However, |
| 21 | I never dared changing the whole thing. */ |
| 22 | /* 01-17-2002: I think the whole, ugly fixed-thing has to be revived, |
| 23 | because floating point calculations are not guaranteed to be network |
| 24 | safe...however, it can be solved as a data type with operator |
| 25 | overloading, automatic type conversions, etc now - Sven2 */ |
| 26 | /* After some time with synchronous float use, C4Fixed is used again to |
| 27 | work around the problem that different compilers produce different |
| 28 | floating point code, leading to desyncs between linux and windows |
| 29 | engines. */ |
| 30 | |
| 31 | #pragma once |
| 32 | |
| 33 | #include <compare> |
| 34 | #include <math.h> |
| 35 | |
| 36 | #include "StdCompiler.h" |
| 37 | #include "StdAdaptors.h" |
| 38 | |
| 39 | extern long SineTable[9001]; // external table of sine values |
| 40 | |
| 41 | // fixpoint shift (check 64 bit emulation before changing!) |
| 42 | constexpr int32_t FIXED_SHIFT{16}; |
| 43 | // fixpoint factor |
| 44 | constexpr int32_t FIXED_FPF{1 << FIXED_SHIFT}; |
| 45 | |
| 46 | class C4Fixed |
| 47 | { |
| 48 | friend constexpr int fixtoi(const C4Fixed &x); |
| 49 | friend constexpr int fixtoi(const C4Fixed &x, int32_t prec); |
| 50 | friend constexpr C4Fixed itofix(int32_t x); |
| 51 | friend constexpr C4Fixed itofix(int32_t x, int32_t prec); |
| 52 | friend constexpr float fixtof(const C4Fixed &x); |
| 53 | friend constexpr C4Fixed ftofix(float x); |
| 54 | |
| 55 | friend inline void CompileFunc(C4Fixed &rValue, StdCompiler *pComp); |
| 56 | |
| 57 | public: |
| 58 | int32_t val; // internal value |
| 59 | |
| 60 | public: |
| 61 | constexpr C4Fixed() {} |
| 62 | constexpr C4Fixed(const C4Fixed &rCpy) : val(rCpy.val) {} |
| 63 | constexpr C4Fixed &operator=(const C4Fixed &other) |
| 64 | { |
| 65 | val = other.val; |
| 66 | return *this; |
| 67 | } |
| 68 | |
| 69 | // Conversion must be done by the conversion routines itofix, fixtoi, ftofix and fixtof |
| 70 | // in order to be backward compatible, so everything is private. |
| 71 | |
| 72 | private: |
| 73 | explicit constexpr C4Fixed(int32_t iVal) |
| 74 | : val(iVal * FIXED_FPF) {} |
| 75 | explicit constexpr C4Fixed(int32_t iVal, int32_t iPrec) |
| 76 | : val(iPrec < FIXED_FPF |
| 77 | ? iVal * (FIXED_FPF / iPrec) + (iVal * (FIXED_FPF % iPrec)) / iPrec |
| 78 | : int32_t(int64_t(iVal) * FIXED_FPF / iPrec) |
| 79 | ) {} |
| 80 | explicit constexpr C4Fixed(float fVal) |
| 81 | : val(static_cast<int32_t>(fVal * float(FIXED_FPF))) {} |
| 82 | |
| 83 | // round to int |
| 84 | constexpr int32_t to_int() const |
| 85 | { |
| 86 | int32_t r = val; |
| 87 | // be carefull not to overflow |
| 88 | r += (val <= 0x7fffffff - FIXED_FPF / 2) * FIXED_FPF / 2; |
| 89 | // ensure that -x.50 is rounded to -(x+1) |
| 90 | r -= (r < 0); |
| 91 | r >>= FIXED_SHIFT; |
| 92 | // round 32767.5 to 32768 (not that anybody cares) |
| 93 | r += (val > 0x7fffffff - FIXED_FPF / 2); |
| 94 | return r; |
| 95 | } |
| 96 | |
| 97 | constexpr int32_t to_int(int32_t prec) const |
| 98 | { |
| 99 | int64_t r = val; |
| 100 | r *= prec; |
| 101 | r += FIXED_FPF / 2; |
| 102 | r -= (r < 0); |
| 103 | r >>= FIXED_SHIFT; |
| 104 | return int32_t(r); |
| 105 | } |
| 106 | |
| 107 | // convert to floating point value |
| 108 | constexpr float to_float() const |
| 109 | { |
| 110 | return float(val) / float(FIXED_FPF); |
| 111 | } |
| 112 | |
| 113 | public: |
| 114 | // set integer (allowed for historic reasons) |
| 115 | constexpr C4Fixed &operator=(int32_t x) { return *this = C4Fixed(x); } |
| 116 | |
| 117 | // test value |
| 118 | constexpr operator bool() const { return !!val; } |
| 119 | constexpr bool operator!() const { return !val; } |
| 120 | |
| 121 | // arithmetic operations |
| 122 | constexpr C4Fixed &operator+=(const C4Fixed &fVal2) |
| 123 | { |
| 124 | val += fVal2.val; |
| 125 | return *this; |
| 126 | } |
| 127 | |
| 128 | constexpr C4Fixed &operator-=(const C4Fixed &fVal2) |
| 129 | { |
| 130 | val -= fVal2.val; |
| 131 | return *this; |
| 132 | } |
| 133 | |
| 134 | constexpr C4Fixed &operator*=(const C4Fixed &fVal2) |
| 135 | { |
| 136 | val = int32_t((int64_t(val) * fVal2.val) / FIXED_FPF); |
| 137 | return *this; |
| 138 | } |
| 139 | |
| 140 | constexpr C4Fixed &operator*=(int32_t iVal2) |
| 141 | { |
| 142 | val *= iVal2; |
| 143 | return *this; |
| 144 | } |
| 145 | |
| 146 | constexpr C4Fixed &operator/=(const C4Fixed &fVal2) |
| 147 | { |
| 148 | val = int32_t((int64_t(val) * FIXED_FPF) / fVal2.val); |
| 149 | return *this; |
| 150 | } |
| 151 | |
| 152 | constexpr C4Fixed &operator/=(int32_t iVal2) |
| 153 | { |
| 154 | val /= iVal2; |
| 155 | return *this; |
| 156 | } |
| 157 | |
| 158 | constexpr C4Fixed operator-() const |
| 159 | { |
| 160 | C4Fixed fr; fr.val = -val; return fr; |
| 161 | } |
| 162 | |
| 163 | constexpr C4Fixed operator+() const |
| 164 | { |
| 165 | return *this; |
| 166 | } |
| 167 | |
| 168 | constexpr bool operator== (const C4Fixed &fVal2) const { return val == fVal2.val; } |
| 169 | constexpr std::strong_ordering operator<=>(const C4Fixed &value) const { return val <=> value.val; } |
| 170 | |
| 171 | // and wrappers |
| 172 | constexpr C4Fixed &operator+=(int32_t iVal2) { return operator += (fVal2: C4Fixed(iVal2)); } |
| 173 | constexpr C4Fixed &operator-=(int32_t iVal2) { return operator -= (fVal2: C4Fixed(iVal2)); } |
| 174 | |
| 175 | constexpr C4Fixed operator+(const C4Fixed &fVal2) const { return C4Fixed(*this) += fVal2; } |
| 176 | constexpr C4Fixed operator-(const C4Fixed &fVal2) const { return C4Fixed(*this) -= fVal2; } |
| 177 | constexpr C4Fixed operator*(const C4Fixed &fVal2) const { return C4Fixed(*this) *= fVal2; } |
| 178 | constexpr C4Fixed operator/(const C4Fixed &fVal2) const { return C4Fixed(*this) /= fVal2; } |
| 179 | |
| 180 | constexpr C4Fixed operator+(int32_t iVal2) const { return C4Fixed(*this) += iVal2; } |
| 181 | constexpr C4Fixed operator-(int32_t iVal2) const { return C4Fixed(*this) -= iVal2; } |
| 182 | constexpr C4Fixed operator*(int32_t iVal2) const { return C4Fixed(*this) *= iVal2; } |
| 183 | constexpr C4Fixed operator/(int32_t iVal2) const { return C4Fixed(*this) /= iVal2; } |
| 184 | |
| 185 | constexpr bool operator==(int32_t iVal2) const { return operator==(fVal2: C4Fixed(iVal2)); } |
| 186 | constexpr std::strong_ordering operator<=>(const std::int32_t value) const { return *this <=> C4Fixed{value}; } |
| 187 | |
| 188 | C4Fixed sin_deg() const |
| 189 | { |
| 190 | // adjust angle |
| 191 | int32_t v = int32_t((int64_t(val) * 100) / FIXED_FPF); if (v < 0) v = 18000 - v; v %= 36000; |
| 192 | // get sine |
| 193 | C4Fixed fr; |
| 194 | switch (v / 9000) |
| 195 | { |
| 196 | case 0: fr.val = +SineTable[v]; break; |
| 197 | case 1: fr.val = +SineTable[18000 - v]; break; |
| 198 | case 2: fr.val = -SineTable[v - 18000]; break; |
| 199 | case 3: fr.val = -SineTable[36000 - v]; break; |
| 200 | } |
| 201 | return fr; |
| 202 | } |
| 203 | |
| 204 | C4Fixed cos_deg() const |
| 205 | { |
| 206 | // adjust angle |
| 207 | int32_t v = int32_t((int64_t(val) * 100) / FIXED_FPF); if (v < 0) v = -v; v %= 36000; |
| 208 | // get cosine |
| 209 | C4Fixed fr; |
| 210 | switch (v / 9000) |
| 211 | { |
| 212 | case 0: fr.val = +SineTable[9000 - v]; break; |
| 213 | case 1: fr.val = -SineTable[v - 9000]; break; |
| 214 | case 2: fr.val = -SineTable[27000 - v]; break; |
| 215 | case 3: fr.val = +SineTable[v - 27000]; break; |
| 216 | } |
| 217 | return fr; |
| 218 | } |
| 219 | }; |
| 220 | |
| 221 | // conversion |
| 222 | constexpr float fixtof(const C4Fixed &x) { return x.to_float(); } |
| 223 | constexpr C4Fixed ftofix(float x) { return C4Fixed(x); } |
| 224 | constexpr int fixtoi(const C4Fixed &x) { return x.to_int(); } |
| 225 | constexpr int fixtoi(const C4Fixed &x, int32_t prec) { return x.to_int(prec); } |
| 226 | constexpr C4Fixed itofix(int32_t x) { return C4Fixed(x); } |
| 227 | constexpr C4Fixed itofix(int32_t x, int32_t prec) { return C4Fixed(x, prec); } |
| 228 | |
| 229 | // additional functions |
| 230 | inline C4Fixed Sin(const C4Fixed &fAngle) { return fAngle.sin_deg(); } |
| 231 | inline C4Fixed Cos(const C4Fixed &fAngle) { return fAngle.cos_deg(); } |
| 232 | constexpr C4Fixed FIXED100(int x) { return itofix(x, prec: 100); } |
| 233 | constexpr C4Fixed FIXED256(int x) { C4Fixed r; r.val = x * FIXED_FPF / 256; return r; } |
| 234 | constexpr C4Fixed FIXED10(int x) { return itofix(x, prec: 10); } |
| 235 | |
| 236 | // define 0 |
| 237 | inline constexpr C4Fixed Fix0 = itofix(x: 0); |
| 238 | |
| 239 | // conversion... |
| 240 | // note: keep out! really dirty casts! |
| 241 | inline void FLOAT_TO_FIXED(C4Fixed *pVal) |
| 242 | { |
| 243 | *pVal = ftofix(x: *reinterpret_cast<float *>(pVal)); |
| 244 | } |
| 245 | |
| 246 | #undef inline |
| 247 | |
| 248 | // CompileFunc for C4Fixed |
| 249 | inline void CompileFunc(C4Fixed &rValue, StdCompiler *pComp) |
| 250 | { |
| 251 | char cFormat = 'F'; |
| 252 | try |
| 253 | { |
| 254 | // Read/write type |
| 255 | pComp->Character(rChar&: cFormat); |
| 256 | } |
| 257 | catch (const StdCompiler::NotFoundException &) |
| 258 | { |
| 259 | // Expect old format if not found |
| 260 | cFormat = 'F'; |
| 261 | } |
| 262 | // Read value (as int32_t) |
| 263 | pComp->Value(rStruct: mkCastAdapt<int32_t>(rValue)); |
| 264 | // convert, if neccessary |
| 265 | if (cFormat == 'f') |
| 266 | FLOAT_TO_FIXED(pVal: &rValue); |
| 267 | } |
| 268 | |