]> git.lizzy.rs Git - minetest.git/blob - src/particles.h
Explicitly initialize value of particle parameter. Fixes #12621.
[minetest.git] / src / particles.h
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #pragma once
21
22 #include <string>
23 #include <sstream>
24 #include <vector>
25 #include <ctgmath>
26 #include <type_traits>
27 #include "irrlichttypes_bloated.h"
28 #include "tileanimation.h"
29 #include "mapnode.h"
30 #include "util/serialize.h"
31 #include "util/numeric.h"
32
33 // This file defines the particle-related structures that both the server and
34 // client need. The ParticleManager and rendering is in client/particles.h
35
36 namespace ParticleParamTypes
37 {
38         template <bool cond, typename T>
39         using enableIf = typename std::enable_if<cond, T>::type;
40         // std::enable_if_t does not appear to be present in GCC????
41         // std::is_enum_v also missing. wtf. these are supposed to be
42         // present as of c++14
43
44         template<typename T> using BlendFunction = T(float,T,T);
45         #define DECL_PARAM_SRZRS(type) \
46                 void serializeParameterValue  (std::ostream& os, type   v); \
47                 void deSerializeParameterValue(std::istream& is, type&  r);
48         #define DECL_PARAM_OVERLOADS(type) DECL_PARAM_SRZRS(type) \
49                 type interpolateParameterValue(float  fac,  const type a, const type b); \
50                 type pickParameterValue       (float* facs, const type a, const type b);
51
52         DECL_PARAM_OVERLOADS(u8);  DECL_PARAM_OVERLOADS(s8);
53         DECL_PARAM_OVERLOADS(u16); DECL_PARAM_OVERLOADS(s16);
54         DECL_PARAM_OVERLOADS(u32); DECL_PARAM_OVERLOADS(s32);
55         DECL_PARAM_OVERLOADS(f32);
56         DECL_PARAM_OVERLOADS(v2f);
57         DECL_PARAM_OVERLOADS(v3f);
58
59         /* C++ is a strongly typed language. this means that enums cannot be implicitly
60          * cast to integers, as they can be in C. while this may sound good in principle,
61          * it means that our normal serialization functions cannot be called on
62          * enumerations unless they are explicitly cast to a particular type first. this
63          * is problematic, because in C++ enums can have any integral type as an underlying
64          * type, and that type would need to be named everywhere an enumeration is
65          * de/serialized.
66          *
67          * this is obviously not cool, both in terms of writing legible, succinct code,
68          * and in terms of robustness: the underlying type might be changed at some point,
69          * e.g. if a bitmask gets too big for its britches. we could use an equivalent of
70          * `std::to_underlying(value)` everywhere we need to deal with enumerations, but
71          * that's hideous and unintuitive. instead, we supply the following functions to
72          * transparently map enumeration types to their underlying values. */
73
74         template <typename E, enableIf<std::is_enum<E>::value, bool> = true>
75         void serializeParameterValue(std::ostream& os, E k) {
76                 serializeParameterValue(os, (std::underlying_type_t<E>)k);
77         }
78
79         template <typename E, enableIf<std::is_enum<E>::value, bool> = true>
80         void deSerializeParameterValue(std::istream& is, E& k) {
81                 std::underlying_type_t<E> v;
82                 deSerializeParameterValue(is, v);
83                 k = (E)v;
84         }
85
86         /* this is your brain on C++. */
87
88         template <typename T, size_t PN>
89         struct Parameter
90         {
91                 using ValType = T;
92                 using pickFactors = float[PN];
93
94                 T val = T();
95                 using This = Parameter<T, PN>;
96
97                 Parameter() = default;
98
99                 template <typename... Args>
100                 Parameter(Args... args) : val(args...) {}
101
102                 virtual void serialize(std::ostream &os) const
103                         { serializeParameterValue  (os, this->val); }
104                 virtual void deSerialize(std::istream &is)
105                         { deSerializeParameterValue(is, this->val); }
106
107                 virtual T interpolate(float fac, const This& against) const
108                 {
109                         return interpolateParameterValue(fac, this->val, against.val);
110                 }
111
112                 static T pick(float* f, const This& a, const This& b)
113                 {
114                         return pickParameterValue(f, a.val, b.val);
115                 }
116
117                 operator T() const { return val; }
118                 T operator=(T b) { return val = b; }
119
120         };
121
122         template <typename T> T numericalBlend(float fac, T min, T max)
123                 { return min + ((max - min) * fac); }
124
125         template <typename T, size_t N>
126         struct VectorParameter : public Parameter<T,N> {
127                 using This = VectorParameter<T,N>;
128                 template <typename... Args>
129                 VectorParameter(Args... args) : Parameter<T,N>(args...) {}
130         };
131
132         template <typename T, size_t PN>
133         inline std::string dump(const Parameter<T,PN>& p)
134         {
135                 return std::to_string(p.val);
136         }
137
138         template <typename T, size_t N>
139         inline std::string dump(const VectorParameter<T,N>& v)
140         {
141                 std::ostringstream oss;
142                 if (N == 3)
143                         oss << PP(v.val);
144                 else
145                         oss << PP2(v.val);
146                 return oss.str();
147         }
148
149         using u8Parameter  = Parameter<u8,  1>; using s8Parameter  = Parameter<s8,  1>;
150         using u16Parameter = Parameter<u16, 1>; using s16Parameter = Parameter<s16, 1>;
151         using u32Parameter = Parameter<u32, 1>; using s32Parameter = Parameter<s32, 1>;
152
153         using f32Parameter = Parameter<f32, 1>;
154
155         using v2fParameter = VectorParameter<v2f, 2>;
156         using v3fParameter = VectorParameter<v3f, 3>;
157
158         template <typename T>
159         struct RangedParameter
160         {
161                 using ValType = T;
162                 using This = RangedParameter<T>;
163
164                 T min, max;
165                 f32 bias = 0;
166
167                 RangedParameter() = default;
168                 RangedParameter(T _min, T _max)            : min(_min),  max(_max)  {}
169                 template <typename M> RangedParameter(M b) : min(b),     max(b)     {}
170
171                 // these functions handle the old range serialization "format"; bias must
172                 // be manually encoded in a separate part of the stream. NEVER ADD FIELDS
173                 // TO THESE FUNCTIONS
174                 void legacySerialize(std::ostream& os) const
175                 {
176                         min.serialize(os);
177                         max.serialize(os);
178                 }
179                 void legacyDeSerialize(std::istream& is)
180                 {
181                         min.deSerialize(is);
182                         max.deSerialize(is);
183                 }
184
185                 // these functions handle the format used by new fields. new fields go here
186                 void serialize(std::ostream &os) const
187                 {
188                         legacySerialize(os);
189                         writeF32(os, bias);
190                 }
191                 void deSerialize(std::istream &is)
192                 {
193                         legacyDeSerialize(is);
194                         bias = readF32(is);
195                 }
196
197                 This interpolate(float fac, const This against) const
198                 {
199                         This r;
200                         r.min = min.interpolate(fac, against.min);
201                         r.max = max.interpolate(fac, against.max);
202                         r.bias = bias;
203                         return r;
204                 }
205
206                 T pickWithin() const
207                 {
208                         typename T::pickFactors values;
209                         auto p = numericAbsolute(bias) + 1;
210                         for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); ++i) {
211                                 if (bias < 0)
212                                         values[i] = 1.0f - pow(myrand_float(), p);
213                                 else
214                                         values[i] = pow(myrand_float(), p);
215                         }
216                         return T::pick(values, min, max);
217                 }
218
219         };
220
221         template <typename T>
222         inline std::string dump(const RangedParameter<T>& r)
223         {
224                 std::ostringstream s;
225                 s << "range<" << dump(r.min) << " ~ " << dump(r.max);
226                 if (r.bias != 0)
227                         s << " :: " << r.bias;
228                 s << ">";
229                 return s.str();
230         }
231
232         enum class TweenStyle : u8 { fwd, rev, pulse, flicker };
233
234         template <typename T>
235         struct TweenedParameter
236         {
237                 using ValType = T;
238                 using This = TweenedParameter<T>;
239
240                 TweenStyle style = TweenStyle::fwd;
241                 u16 reps = 1;
242                 f32 beginning = 0.0f;
243
244                 T start, end;
245
246                 TweenedParameter() = default;
247                 TweenedParameter(T _start, T _end)          : start(_start),  end(_end) {}
248                 template <typename M> TweenedParameter(M b) : start(b),       end(b) {}
249
250                 T blend(float fac) const
251                 {
252                         // warp time coordinates in accordance w/ settings
253                         if (fac > beginning) {
254                                 // remap for beginning offset
255                                 auto len = 1 - beginning;
256                                 fac -= beginning;
257                                 fac /= len;
258
259                                 // remap for repetitions
260                                 fac *= reps;
261                                 if (fac > 1) // poor man's modulo
262                                         fac -= (decltype(reps))fac;
263
264                                 // remap for style
265                                 switch (style) {
266                                         case TweenStyle::fwd: /* do nothing */  break;
267                                         case TweenStyle::rev: fac = 1.0f - fac; break;
268                                         case TweenStyle::pulse:
269                                         case TweenStyle::flicker: {
270                                                 if (fac > 0.5f) {
271                                                         fac = 1.f - (fac*2.f - 1.f);
272                                                 } else {
273                                                         fac = fac * 2;
274                                                 }
275                                                 if (style == TweenStyle::flicker) {
276                                                         fac *= myrand_range(0.7f, 1.0f);
277                                                 }
278                                         }
279                                 }
280                                 if (fac>1.f)
281                                         fac = 1.f;
282                                 else if (fac<0.f)
283                                         fac = 0.f;
284                         } else {
285                                 fac = (style == TweenStyle::rev) ? 1.f : 0.f;
286                         }
287
288                         return start.interpolate(fac, end);
289                 }
290
291                 void serialize(std::ostream &os) const
292                 {
293                         writeU8(os, static_cast<u8>(style));
294                         writeU16(os, reps);
295                         writeF32(os, beginning);
296                         start.serialize(os);
297                         end.serialize(os);
298                 }
299                 void deSerialize(std::istream &is)
300                 {
301                         style = static_cast<TweenStyle>(readU8(is));
302                         reps = readU16(is);
303                         beginning = readF32(is);
304                         start.deSerialize(is);
305                         end.deSerialize(is);
306                 }
307         };
308
309         template <typename T>
310         inline std::string dump(const TweenedParameter<T>& t)
311         {
312                 std::ostringstream s;
313                 const char* icon;
314                 switch (t.style) {
315                         case TweenStyle::fwd: icon = "→"; break;
316                         case TweenStyle::rev: icon = "←"; break;
317                         case TweenStyle::pulse: icon = "↔"; break;
318                         case TweenStyle::flicker: icon = "↯"; break;
319                 }
320                 s << "tween<";
321                 if (t.reps != 1)
322                         s << t.reps << "x ";
323                 s << dump(t.start) << " "<<icon<<" " << dump(t.end) << ">";
324                 return s.str();
325         }
326
327         enum class AttractorKind : u8 { none, point, line, plane };
328         enum class BlendMode     : u8 { alpha, add, sub, screen  };
329
330         // these are consistently-named convenience aliases to make code more readable without `using ParticleParamTypes` declarations
331         using v3fRange = RangedParameter<v3fParameter>;
332         using f32Range = RangedParameter<f32Parameter>;
333
334         using v2fTween      = TweenedParameter<v2fParameter>;
335         using v3fTween      = TweenedParameter<v3fParameter>;
336         using f32Tween      = TweenedParameter<f32Parameter>;
337         using v3fRangeTween = TweenedParameter<v3fRange>;
338         using f32RangeTween = TweenedParameter<f32Range>;
339
340         #undef DECL_PARAM_SRZRS
341         #undef DECL_PARAM_OVERLOADS
342 }
343
344 struct ParticleTexture
345 {
346         bool animated = false;
347         ParticleParamTypes::BlendMode blendmode = ParticleParamTypes::BlendMode::alpha;
348         TileAnimationParams animation;
349         ParticleParamTypes::f32Tween alpha{1.0f};
350         ParticleParamTypes::v2fTween scale{v2f(1.0f)};
351 };
352
353 struct ServerParticleTexture : public ParticleTexture
354 {
355         std::string string;
356         void serialize(std::ostream &os, u16 protocol_ver, bool newPropertiesOnly = false) const;
357         void deSerialize(std::istream &is, u16 protocol_ver, bool newPropertiesOnly = false);
358 };
359
360 struct CommonParticleParams
361 {
362         bool collisiondetection = false;
363         bool collision_removal = false;
364         bool object_collision = false;
365         bool vertical = false;
366         ServerParticleTexture texture;
367         struct TileAnimationParams animation;
368         u8 glow = 0;
369         MapNode node;
370         u8 node_tile = 0;
371
372         CommonParticleParams() {
373                 animation.type = TAT_NONE;
374                 node.setContent(CONTENT_IGNORE);
375         }
376
377         /* This helper is useful for copying params from
378          * ParticleSpawnerParameters to ParticleParameters */
379         inline void copyCommon(CommonParticleParams &to) const {
380                 to.collisiondetection = collisiondetection;
381                 to.collision_removal = collision_removal;
382                 to.object_collision = object_collision;
383                 to.vertical = vertical;
384                 to.texture = texture;
385                 to.animation = animation;
386                 to.glow = glow;
387                 to.node = node;
388                 to.node_tile = node_tile;
389         }
390 };
391
392 struct ParticleParameters : CommonParticleParams
393 {
394         v3f pos, vel, acc, drag;
395         f32 size = 1, expirationtime = 1;
396         ParticleParamTypes::f32Range bounce;
397         ParticleParamTypes::v3fRange jitter;
398
399         void serialize(std::ostream &os, u16 protocol_ver) const;
400         void deSerialize(std::istream &is, u16 protocol_ver);
401 };
402
403 struct ParticleSpawnerParameters : CommonParticleParams
404 {
405         u16 amount = 1;
406         f32 time = 1;
407
408         std::vector<ServerParticleTexture> texpool;
409
410         ParticleParamTypes::v3fRangeTween
411                 pos, vel, acc, drag, radius, jitter;
412
413         ParticleParamTypes::AttractorKind
414                 attractor_kind;
415         ParticleParamTypes::v3fTween
416                 attractor_origin, attractor_direction;
417         // object IDs
418         u16 attractor_attachment = 0,
419             attractor_direction_attachment = 0;
420         // do particles disappear when they cross the attractor threshold?
421         bool attractor_kill = true;
422
423         ParticleParamTypes::f32RangeTween
424                 exptime{1.0f},
425                 size   {1.0f},
426                 attract{0.0f},
427                 bounce {0.0f};
428
429         // For historical reasons no (de-)serialization methods here
430 };