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 | |
12 | namespace 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 | } |