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
39extern long SineTable[9001]; // external table of sine values
40
41// fixpoint shift (check 64 bit emulation before changing!)
42constexpr int32_t FIXED_SHIFT{16};
43// fixpoint factor
44constexpr int32_t FIXED_FPF{1 << FIXED_SHIFT};
45
46class 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
57public:
58 int32_t val; // internal value
59
60public:
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
72private:
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
113public:
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
222constexpr float fixtof(const C4Fixed &x) { return x.to_float(); }
223constexpr C4Fixed ftofix(float x) { return C4Fixed(x); }
224constexpr int fixtoi(const C4Fixed &x) { return x.to_int(); }
225constexpr int fixtoi(const C4Fixed &x, int32_t prec) { return x.to_int(prec); }
226constexpr C4Fixed itofix(int32_t x) { return C4Fixed(x); }
227constexpr C4Fixed itofix(int32_t x, int32_t prec) { return C4Fixed(x, prec); }
228
229// additional functions
230inline C4Fixed Sin(const C4Fixed &fAngle) { return fAngle.sin_deg(); }
231inline C4Fixed Cos(const C4Fixed &fAngle) { return fAngle.cos_deg(); }
232constexpr C4Fixed FIXED100(int x) { return itofix(x, prec: 100); }
233constexpr C4Fixed FIXED256(int x) { C4Fixed r; r.val = x * FIXED_FPF / 256; return r; }
234constexpr C4Fixed FIXED10(int x) { return itofix(x, prec: 10); }
235
236// define 0
237inline constexpr C4Fixed Fix0 = itofix(x: 0);
238
239// conversion...
240// note: keep out! really dirty casts!
241inline 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
249inline 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