1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#pragma once
4
5#include "BsCorePrerequisites.h"
6#include "Image/BsColor.h"
7#include "Math/BsVector3.h"
8#include "Math/BsVector2.h"
9#include "Utility/BsBitwise.h"
10#include "Allocators/BsGroupAlloc.h"
11
12namespace bs
13{
14 /** @addtogroup Particles-Internal
15 * @{
16 */
17
18 /** Handles buffers containing particle data and their allocation/deallocation. */
19 struct ParticleSetData
20 {
21 /** Creates a new set and allocates enough space for @p capacity particles. */
22 ParticleSetData(UINT32 capacity)
23 :capacity(capacity)
24 {
25 allocate();
26 }
27
28 /**
29 * Creates a new set, allocates enough space for @p capacity particles and initializes the particles by copying
30 * them from the @p other set.
31 */
32 ParticleSetData(UINT32 capacity, const ParticleSetData& other)
33 :capacity(capacity)
34 {
35 allocate();
36 copy(other);
37 }
38
39 /** Moves data from @p other to this set. */
40 ParticleSetData(ParticleSetData&& other) noexcept
41 {
42 move(other);
43 }
44
45 /** Moves data from @p other to this set. */
46 ParticleSetData& operator=(ParticleSetData&& other) noexcept
47 {
48 if(this != &other)
49 {
50 free();
51 move(other);
52 }
53
54 return *this;
55 }
56
57 ~ParticleSetData()
58 {
59 free();
60 }
61
62 UINT32 capacity = 0;
63
64 Vector3* prevPosition = nullptr;
65 Vector3* position = nullptr;
66 Vector3* velocity = nullptr;
67 Vector3* size = nullptr;
68 Vector3* rotation = nullptr;
69 float* initialLifetime = nullptr;
70 float* lifetime = nullptr;
71 RGBA* color = nullptr;
72 UINT32* seed = nullptr;
73 float* frame = nullptr;
74 UINT32* indices = nullptr;
75
76 private:
77 /**
78 * Allocates a new set of buffers with enough space to store number of particles equal to the current capacity. *
79 * Called must ensure any previously allocated buffer is freed by calling free().
80 */
81 void allocate()
82 {
83 alloc.
84 reserve<Vector3>(capacity).
85 reserve<Vector3>(capacity).
86 reserve<Vector3>(capacity).
87 reserve<Vector3>(capacity).
88 reserve<Vector3>(capacity).
89 reserve<float>(capacity).
90 reserve<float>(capacity).
91 reserve<RGBA>(capacity).
92 reserve<UINT32>(capacity).
93 reserve<float>(capacity).
94 reserve<UINT32>(capacity).
95 init();
96
97 prevPosition = alloc.alloc<Vector3>(capacity);
98 position = alloc.alloc<Vector3>(capacity);
99 velocity = alloc.alloc<Vector3>(capacity);
100 size = alloc.alloc<Vector3>(capacity);
101 rotation = alloc.alloc<Vector3>(capacity);
102 lifetime = alloc.alloc<float>(capacity);
103 initialLifetime = alloc.alloc<float>(capacity);
104 color = alloc.alloc<RGBA>(capacity);
105 seed = alloc.alloc<UINT32>(capacity);
106 frame = alloc.alloc<float>(capacity);
107 indices = alloc.alloc<UINT32>(capacity);
108 }
109
110 /** Frees the internal buffers. */
111 void free()
112 {
113 if(prevPosition) alloc.free(prevPosition);
114 if(position) alloc.free(position);
115 if(velocity) alloc.free(velocity);
116 if(size) alloc.free(size);
117 if(rotation) alloc.free(rotation);
118 if(lifetime) alloc.free(lifetime);
119 if(initialLifetime) alloc.free(initialLifetime);
120 if(color) alloc.free(color);
121 if(seed) alloc.free(seed);
122 if(frame) alloc.free(frame);
123 if(indices) alloc.free(indices);
124
125 alloc.clear();
126 }
127
128 /** Transfers ownership of @p other internal buffers to this object. */
129 void move(ParticleSetData& other)
130 {
131 prevPosition = std::exchange(other.prevPosition, nullptr);
132 position = std::exchange(other.position, nullptr);
133 velocity = std::exchange(other.velocity, nullptr);
134 size = std::exchange(other.size, nullptr);
135 rotation = std::exchange(other.rotation, nullptr);
136 lifetime = std::exchange(other.lifetime, nullptr);
137 initialLifetime = std::exchange(other.initialLifetime, nullptr);
138 color = std::exchange(other.color, nullptr);
139 seed = std::exchange(other.seed, nullptr);
140 frame = std::exchange(other.frame, nullptr);
141 indices = std::exchange(other.indices, nullptr);
142 capacity = std::exchange(other.capacity, 0);
143
144 alloc = std::move(other.alloc);
145 }
146
147 /** Copies data from @p other buffers to this object. */
148 void copy(const ParticleSetData& other)
149 {
150 assert(capacity >= other.capacity);
151
152 bs_copy(prevPosition, other.prevPosition, other.capacity);
153 bs_copy(position, other.position, other.capacity);
154 bs_copy(velocity, other.velocity, other.capacity);
155 bs_copy(size, other.size, other.capacity);
156 bs_copy(rotation, other.rotation, other.capacity);
157 bs_copy(lifetime, other.lifetime, other.capacity);
158 bs_copy(initialLifetime, other.initialLifetime, other.capacity);
159 bs_copy(color, other.color, other.capacity);
160 bs_copy(seed, other.seed, other.capacity);
161 bs_copy(frame, other.frame, other.capacity);
162 bs_copy(indices, other.indices, other.capacity);
163 }
164
165 GroupAlloc alloc;
166 };
167
168 /** Provides a simple and fast way to allocate and deallocate particles. */
169 class ParticleSet : public INonCopyable
170 {
171 /** Determines how much to increase capacity once the cap is reached, in percent. */
172 static constexpr float CAPACITY_SCALE = 1.2f; // 20%
173
174 public:
175 /**
176 * Constructs a new particle set with enough space to hold @p capacity particles. The set will automatically
177 * grow to larger capacity if the limit is reached.
178 */
179 ParticleSet(UINT32 capacity)
180 :mParticles(capacity)
181 { }
182
183 /**
184 * Allocates a number of new particles and returns the index to the particle. Note that the returned index is not
185 * persistent and can become invalid after a call to freeParticle(). Returns the index to the first allocated
186 * particle.
187 */
188 UINT32 allocParticles(UINT32 count)
189 {
190 const UINT32 particleIdx = mCount;
191 mCount += count;
192
193 if(mCount > mParticles.capacity)
194 {
195 const auto newCapacity = (UINT32)(mCount * CAPACITY_SCALE);
196 ParticleSetData newData(newCapacity, mParticles);
197 mParticles = std::move(newData);
198 }
199
200 const UINT32 particleEnd = particleIdx + count;
201 if(particleEnd > mMaxIndex)
202 {
203 for (; mMaxIndex < particleEnd; mMaxIndex++)
204 mParticles.indices[mMaxIndex] = mMaxIndex;
205 }
206
207 return particleIdx;
208 }
209
210 /** Deallocates a particle. Can invalidate particle indices. */
211 void freeParticle(UINT32 idx)
212 {
213 // Note: We always keep the active particles sequential. This makes it faster to iterate over all particles, but
214 // increases the cost when removing particles. Considering iteration should happen many times per-particle,
215 // while removal will happen only once, this should be the more performant approach, but will likely be worth
216 // profiling in the future. An alternative approach is to flag dead particles without moving them.
217
218 assert(idx < mCount);
219
220 const UINT32 lastIdx = mCount - 1;
221 if(idx != lastIdx)
222 {
223 std::swap(mParticles.prevPosition[idx], mParticles.prevPosition[lastIdx]);
224 std::swap(mParticles.position[idx], mParticles.position[lastIdx]);
225 std::swap(mParticles.velocity[idx], mParticles.velocity[lastIdx]);
226 std::swap(mParticles.size[idx], mParticles.size[lastIdx]);
227 std::swap(mParticles.rotation[idx], mParticles.rotation[lastIdx]);
228 std::swap(mParticles.lifetime[idx], mParticles.lifetime[lastIdx]);
229 std::swap(mParticles.initialLifetime[idx], mParticles.initialLifetime[lastIdx]);
230 std::swap(mParticles.color[idx], mParticles.color[lastIdx]);
231 std::swap(mParticles.seed[idx], mParticles.seed[lastIdx]);
232 std::swap(mParticles.frame[idx], mParticles.frame[lastIdx]);
233 std::swap(mParticles.indices[idx], mParticles.indices[lastIdx]);
234 }
235
236 mCount--;
237 }
238
239 /** Frees all active partices past the provided particle count (0 to clear all particles). */
240 void clear(UINT32 numPartices = 0)
241 {
242 if(mCount > numPartices)
243 mCount = numPartices;
244 }
245
246 /** Returns all data about the particles. Active particles are always sequential at the start of the buffer. */
247 ParticleSetData& getParticles() { return mParticles; }
248
249 /** Returns all data about the particles. Active particles are always sequential at the start of the buffer. */
250 const ParticleSetData& getParticles() const { return mParticles; }
251
252 /** Returns the number of particles that are currently active. */
253 UINT32 getParticleCount() const { return mCount; }
254
255 /**
256 * Calculates the size of a texture required for storing the data of this particle set. The texture is assumed
257 * to be square.
258 */
259 UINT32 determineTextureSize() const
260 {
261 const UINT32 count = std::max(2U, getParticleCount());
262
263 UINT32 width = Bitwise::nextPow2(count);
264 UINT32 height = 1;
265
266 while (width > height)
267 {
268 width /= 2;
269 height *= 2;
270 }
271
272 // Make it square
273 return height;
274 }
275
276 private:
277 ParticleSetData mParticles;
278 UINT32 mCount = 0;
279 UINT32 mMaxIndex = 0;
280 };
281
282 /** @} */
283}