2D Game Engine

Project Stats:

Name: IGArt
Project type: Student project
Production Time: 8 weeks
Date: 2018/03-2018/05
Engine/language: C++
Platforms: PC
Tools: Visual Studio, Perforce, Jira
Team members: 9 programming students

Project Description

A 2d platformer game engine inspired by the UbiArt framework.

My contributions

Particle system

I created a particle system inspired by the particle system in Unity.

Particle system curve editor

To change the parameters of particles over their lifetime, I utilized a curve editor in Imgui. Getting the curve editor to function in Imgui was challenging. I tried a few versions of the open-source LumixEngine’s curve editor. I had to settle for an old broken version and fix that because that was most compatible with the version of Imgui we were using. We could not change the version of Imgui because the behavior editor was dependent on that and we had already put a lot of work in that editor.

Random between two curves

In order to enable the editing of a random value between two curves in the editor, I implemented a double curve editor.

Code

Code snippets - particle_system_component.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
#pragma once

/**
* @file sprite_component.h
* @author Kit van de Bunt
* @date 27 mar 2018
* @brief iga::SpriteComponent class header.
*
* This file contains a class that is used in entities with sprites.
* @see iga::Renderer
* @see iga::RenderListener
* @see iga::BaseRenderingComponent
*/

#include <Graphics\base_rendering_component.h>
#include <Graphics\base_group_rendering_component.h>
#include <vectormath\vectormath.hpp>

#include <memory>
#include <defines.h>

#include <cereal/cereal.hpp>
#include <cereal/types/base_class.hpp>
#include <cereal/types/polymorphic.hpp>

#include <cereal/cereal.hpp>
#include <cereal/types/memory.hpp>

/**
* ImGui namespace.
*/
namespace ImGui
{
    class IgaCurveEditor;
    class IgaCurveEditorDouble;
    class IgaCurveEditor3;
    class IgaCurveEditor3Double;
}

/**
* IGArt namespace.
*/
namespace iga {

    /**
    * iga::Particle. Holds per particle variables.
    */
    struct Particle {
        bool alive_; /**< true if particle is alive */
        float life_time_; /**< time left alive */
        float total_live_time_; /**< lifetime given to particle at spawn */
        Vector3 velocity_;/** particles velocity */
        Vector3 angular_velocity_;/** particles velocity */
        Vector3 position_;/** particle position */
        Vector3 rotation_;/** particles rotation */
        Vector3 scale_;/** particle scale */
        Vector4 color_;/** particle color */
        Vector3 random_velocity_between_curves_; /** lerp value for velocity random between two curves */
        Vector3 random_angular_velocity_between_curves_; /** lerp value for angular velocity random between two curves */
    };

    class Resource;

    /**
    * iga::ParticleSystemComponent. This is a component used by entities that are represented by a particleSystem.
    * Inherits iga::BaseGroupRenderingComponent.
    * @see iga::Component
    * @see iga::RenderListener
    * @see iga::BaseGroupRenderingComponent
    */
    class ParticleSystemComponent final : public BaseGroupRenderingComponent {

    public:
        /**
        *   Cereal versions of iga::ParticleSystemComponent
        */
        enum class Version {
            DEFAULT = 0, /**< Default normal version. */
            FIRST, /**< Default normal version. */
            TEXTURE,/**< Texture added. */
            ROTATION,/**< Rotation added. */
            COLOR,/**< Color added. */
            COLOR2,/**< Color added version 2. */
            MINIMUM_VELOCITY,/**< Minimum velocity added. */
            LATEST /**< Latest version. */
        };

        /**
        * Constructor.
        */
        ParticleSystemComponent();
        /**
        *  Custom constructor with iga::entity that will own the component.
        *  @param a_entity is a weak pointer to an iga::Entity that is to be the owner of this component.
        */
        ParticleSystemComponent(std::weak_ptr<Entity> a_entity);

        /**
        * Destructor.
        */
        virtual ~ParticleSystemComponent();

        /**
        * Gets called when it gets created. Is used as an initialization function.
        */
        virtual void OnCreate(bool a_on_load = false) override;
        /**
        * Updates the component.
        */
        virtual void Update() override;
        /**
        * Gets called after the update has happened.
        */
        virtual void PostUpdate() override;

        /**
        *  Function to load a texture for this particle system from a path.
        *  @param a_path A const std::string reference that contains the path to the texture.
        */
        void SetTexture(const std::string& a_path);

        /**
        * Virtual function that gets render data when it wants to build render data.
        */
        virtual std::vector<RenderDataStruct>* OnBuildRenderData() override;

        /**
        *  Used to show information in the editor : Inspector
        */
        virtual void Inspect() override;
        /**
        *  Used to save and load iga::ParticleSystemComponent
        */
        template<class Archive>
        void serialize(Archive & a_archive, uint32 const a_version);

        ModifierStruct modifiers_; /**< Modifier struct to modify the sprite with. @see iga::ModifierStruct */

    protected:


    private:
        std::shared_ptr<Resource> texture_resource_; /**< The texture resource of this sprite. @see iga::Resource @see iga::Texture */
        std::shared_ptr<Resource> mesh_resource_; /**< The mesh resource of a sprite. @see iga::Resource @see iga::Texture */

        std::vector<Particle> particle_vector_; //** vector that cointains all particles in the particle system (pooled) */
        std::vector<RenderDataStruct> render_data_struct_vector_;/** a render data struct for each particle (pooled) */
        
        void ParticleSystemComponent::GrowPoolCount();/** Grows particle pool by one*/
        void ParticleSystemComponent::SpawnParticle();/** Spawns one partilce*/
        void ParticleSystemComponent::Burst();/** Spawns a burst of particles*/

        void ParticleSystemComponent::StartCollapsingHeaderState();
        void ParticleSystemComponent::EndCollapsingHeaderState();
        
        /** 
            * Random float between -.5 and .5 
            * 
            * @param a_from_0_to_1 if true function returns a number form 0 to 1
            */
        float RandomFloat(bool a_from_0_to_1 = false); 

        // Per particle system editable variables(for imgui)
        int max_particles_; /**< Max particles in the particle system*/
        int max_spawn_per_frame_; /**< Max particles added per frame*/
        bool loop_;/**< If true particle system is looping */
        bool burst_;/**< If particle spawns bursts of particles */
        bool burst_once_;/**< True if burst happens once */
        float burst_time_;/**< Time between bursts */
        int burst_count_;/**< Amount of spawns per burst */
        float spawn_time_; /**< When loop true time between each particle spawn */
        float particle_life_time_;/**< Particle life time on particle spawn */
        float particle_life_time_random_;/**< Value multiplied by a random number between -1 and 1 and added to each particles life time on spawn */
        Vector3 spawn_position_;/**< Particles pawn offset from the owners transform */
        Vector3 spawn_rotation_;/**<Particle spawn rotation */
        Vector3 random_spawn_rotion_;/**<Random particle spawn rotation offset*/
        float minimum_velocity_magnitude_ = 0;/**< minimum added velocity per frame (only works when the magnitude of the velocity is more then 0)*/

        int velocityMode; /**< 0 = constant, 1 = velocity over lifetime */
        Vector3 spawn_velocity_;/**< Particles velocity on spawn */
        Vector3 spawn_velocity_random_;/**< Particles velocity on spawn */
        std::shared_ptr<ImGui::IgaCurveEditor3> velocity_over_life_; /**< Size over lifetime curves */
        std::shared_ptr<ImGui::IgaCurveEditor3Double> velocity_over_life_between_curves; /**< Size over lifetime double curves for velocity over lifetime random between 2 curves*/

        int angular_velocity_mode_; /**< 0 = constant, 1 = angular velocity over lifetime */
        Vector3 spawn_angular_velocity_;/**< Particles velocity on spawn */
        Vector3 spawn_angular_velocity_random_;/**< Particles angular velocity on spawn */
        std::shared_ptr<ImGui::IgaCurveEditor3> angular_velocity_over_life_; /**< Size over lifetime curves */
        std::shared_ptr<ImGui::IgaCurveEditor3Double> angular_velocity_over_life_between_curves; /**< Size over lifetime double curves for velocity over lifetime random between 2 curves*/

        Vector3 spawn_range_;/**< Area in wish the particles spawn */

        int scaleMode; /**< 0 = constant, 1 = size over lifetime */
        Vector3 start_scale_; /** Particles scale on spawn*/
        Vector3 start_scale_random_;/** Value multiplied by a random number between -1 and 1 and added to each particles scale on spawn */
        std::shared_ptr<ImGui::IgaCurveEditor> size_over_life_; /**< Size over lifetime curves */

        Vector4 start_color_; /**<Particle spawn color */
        Vector4 start_color_random_offset_; /**<Particle spawn color random offset*/
        std::shared_ptr<ImGui::IgaCurveEditor> color_over_life_r_; /** Red color over lifetime curve*/
        std::shared_ptr<ImGui::IgaCurveEditor> color_over_life_g_; /** Green color over lifetime curve*/
        std::shared_ptr<ImGui::IgaCurveEditor> color_over_life_b_; /** Blue color over lifetime curve*/
        std::shared_ptr<ImGui::IgaCurveEditor> color_over_life_a_; /** Alpha color over lifetime curve*/

        // particle system internal variables (not for imgui)
        float spawn_timer_;/**< Subtract delta time every frame if < 0 particle spawns */ 
        float burst_timer_;/**< Subtract delta time every frame if < 0 burst hapens */
        bool burst_executed_;/**< if burst_once_ = true this gets sets to tue after one burst  */
        int color_over_lifetime_ui_state_;/**< stores the state off the color over life time ui */
        int collapsing_header_state_ = 0;/**< The collapsing header that is open */
        int collapsing_header_state_last_frame_ = -2;/**< Used to open collapsed headers with a frame delay to prefent 2 form being drawn in one frame */
        bool collapsing_header_open_;/**< Internal usage stores if current processed collapsing header is open*/
        int collapsing_header_id_;/**< Internal usage stores the id of the collapsing header that is being processed*/
        bool if_one_collapsing_header_open_; /**< Internal usage If one collepsing header is open set to true*/
        bool following_other = false;
        bool follow_object_alive = true;
        float life_time_after_follow_object_destroyed = 1.0f;

        Vector3 last_follow_position;


#ifdef EDITOR
       char texture_path_buffer_[256] = " "; /**< Editor only variable. A char buffer for the texture path text box. */
#endif

    };

    template<class Archive>
    inline void ParticleSystemComponent::serialize(Archive &a_archive, uint32 const a_version) {
        switch(static_cast<Version>(a_version)) {
            case Version::LATEST:
            case Version::MINIMUM_VELOCITY:
                a_archive(cereal::make_nvp("minimum_velocity_magnitude_", minimum_velocity_magnitude_));

            case Version::COLOR2:
                a_archive(cereal::make_nvp("start_color_", start_color_));
                a_archive(cereal::make_nvp("start_color_random_offset_", start_color_random_offset_));
                a_archive(cereal::make_nvp("color_over_life_r_", color_over_life_r_));
                a_archive(cereal::make_nvp("color_over_life_g_", color_over_life_g_));
                a_archive(cereal::make_nvp("color_over_life_b_", color_over_life_b_));
                a_archive(cereal::make_nvp("color_over_life_a_", color_over_life_a_));

            case Version::COLOR:
            case Version::ROTATION:
                a_archive(cereal::make_nvp("spawn_rotation_", spawn_rotation_));
                a_archive(cereal::make_nvp("random_spawn_rotion_", random_spawn_rotion_));

                a_archive(cereal::make_nvp("angular_velocity_mode_", angular_velocity_mode_));
                a_archive(cereal::make_nvp("spawn_angular_velocity_", spawn_angular_velocity_));
                a_archive(cereal::make_nvp("spawn_angular_velocity_random_", spawn_angular_velocity_random_));
                a_archive(cereal::make_nvp("angular_velocity_over_life_", angular_velocity_over_life_));
                a_archive(cereal::make_nvp("angular_velocity_over_life_between_curves", angular_velocity_over_life_between_curves));

            case Version::TEXTURE:
                a_archive(cereal::make_nvp("texture_resource", texture_resource_));
            case Version::FIRST:
                a_archive(cereal::make_nvp("max_particles_", max_particles_));
                a_archive(cereal::make_nvp("max_spawn_per_frame_", max_spawn_per_frame_));
                a_archive(cereal::make_nvp("loop_", loop_));
                a_archive(cereal::make_nvp("burst_", burst_));
                a_archive(cereal::make_nvp("burst_once_", burst_once_));
                a_archive(cereal::make_nvp("burst_time_", burst_time_));
                a_archive(cereal::make_nvp("burst_count_", burst_count_));
                a_archive(cereal::make_nvp("spawn_time_", spawn_time_));
                a_archive(cereal::make_nvp("particle_life_time_", particle_life_time_));
                a_archive(cereal::make_nvp("particle_life_time_random_", particle_life_time_random_));
                a_archive(cereal::make_nvp("spawn_position_", spawn_position_));

                a_archive(cereal::make_nvp("velocityMode", velocityMode));
                a_archive(cereal::make_nvp("spawn_velocity_", spawn_velocity_));
                a_archive(cereal::make_nvp("spawn_velocity_random_", spawn_velocity_random_));
                a_archive(cereal::make_nvp("velocity_over_life_", velocity_over_life_));
                a_archive(cereal::make_nvp("velocity_over_life_between_curves", velocity_over_life_between_curves));


                a_archive(cereal::make_nvp("spawn_range_", spawn_range_));

                a_archive(cereal::make_nvp("scaleMode", scaleMode));
                a_archive(cereal::make_nvp("start_scale_", start_scale_));
                a_archive(cereal::make_nvp("start_scale_random_", start_scale_random_));
                a_archive(cereal::make_nvp("size_over_life_", size_over_life_));

                a_archive(cereal::make_nvp("spawn_timer_", spawn_timer_));
                a_archive(cereal::make_nvp("burst_timer_", burst_timer_));
                a_archive(cereal::make_nvp("burst_executed_", burst_executed_));

            case Version::DEFAULT:
                a_archive(cereal::base_class<BaseGroupRenderingComponent>(this));
                break;
        }
    }

    SUBSCRIBECOMPONENT(ParticleSystemComponent)

}

CEREAL_CLASS_VERSION(iga::ParticleSystemComponent, (static_cast<iga::uint32>(iga::ParticleSystemComponent::Version::LATEST) - 1));

Code snippets - particle_system_component.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
#include <ParticleSystem/particle_system_component.h>

#include <ig_art_engine.h>
#ifdef EDITOR
#include <imgui/include/imgui.h>
#include <Utility/util_editor.h>
#endif

#include <imgui_user.h>

#include <stdlib.h>     /* srand, rand */
#include <time.h>       /* time */

#include <cereal/types/base_class.hpp>
#include <cereal/types/polymorphic.hpp>
#include <cereal/archives/json.hpp>
#include <cereal/archives/binary.hpp>

CEREAL_REGISTER_TYPE(iga::ParticleSystemComponent);
CEREAL_REGISTER_POLYMORPHIC_RELATION(iga::BaseGroupRenderingComponent, iga::ParticleSystemComponent);

namespace iga {

    ParticleSystemComponent::ParticleSystemComponent() {}

    ParticleSystemComponent::ParticleSystemComponent(std::weak_ptr<Entity> a_entity) :
        BaseGroupRenderingComponent(a_entity) {
        component_type_ = "ParticleSystemComponent";
        // called on new component

        Vector2 default_curve_values_[4];
        default_curve_values_[0].setX(0);
        default_curve_values_[0].setY(0);
        default_curve_values_[1].setX(.1f);
        default_curve_values_[1].setY(.0f);
        default_curve_values_[2].setX(.9f);
        default_curve_values_[2].setY(.0f);
        default_curve_values_[3].setX(1.0f);
        default_curve_values_[3].setY(.0f);

        // velocity
        velocity_over_life_ = std::make_shared<ImGui::IgaCurveEditor3>(default_curve_values_);
        velocity_over_life_between_curves = std::make_shared<ImGui::IgaCurveEditor3Double>(default_curve_values_);
        velocityMode = 0;
        angular_velocity_mode_ = 0;
        spawn_velocity_ = Vector3(0, .02f, 0);
        spawn_velocity_random_ = Vector3(0, .005f, 0);

        // angular velocity
        angular_velocity_over_life_ = std::make_shared<ImGui::IgaCurveEditor3>(default_curve_values_);
        angular_velocity_over_life_between_curves = std::make_shared<ImGui::IgaCurveEditor3Double>(default_curve_values_);
        spawn_angular_velocity_ = Vector3(0.f);
        spawn_angular_velocity_random_ = Vector3(0.f);

        size_over_life_ = std::make_shared<ImGui::IgaCurveEditor>();

        // particle spawn
        max_particles_ = 60;
        loop_ = false;
        burst_executed_ = false;
        burst_ = true;
        burst_once_ = false;
        burst_time_ = 1.5f;
        burst_count_ = 10;
        spawn_time_ = 0.05f;
        spawn_timer_ = spawn_time_;
        max_spawn_per_frame_ = 100;
        spawn_position_ = Vector3(0, 0, 0);
        particle_life_time_ = 4.0f;
        particle_life_time_random_ = .5f;
        spawn_range_.setX(.5f);
        spawn_range_.setY(.5f);
        spawn_range_.setZ(.5f);
        start_scale_ = Vector3(0.05f);
        start_scale_random_ = Vector3(0.02f);
        scaleMode = 1;

        default_curve_values_[0].setX(0);
        default_curve_values_[0].setY(1);
        default_curve_values_[1].setX(0.1f);
        default_curve_values_[1].setY(1);
        default_curve_values_[2].setX(.9f);
        default_curve_values_[2].setY(1);
        default_curve_values_[3].setX(1);
        default_curve_values_[3].setY(1);

        color_over_life_r_ = std::make_shared<ImGui::IgaCurveEditor>(default_curve_values_);
        color_over_life_g_ = std::make_shared<ImGui::IgaCurveEditor>(default_curve_values_);
        color_over_life_b_ = std::make_shared<ImGui::IgaCurveEditor>(default_curve_values_);
        color_over_life_a_ = std::make_shared<ImGui::IgaCurveEditor>(default_curve_values_);

        start_color_ = Vector4(255.0f);
        start_color_random_offset_ = Vector4(0.0f);

        spawn_rotation_ = Vector3(0.0f);
        random_spawn_rotion_ = Vector3(0.0f);
    }

    ParticleSystemComponent::~ParticleSystemComponent() {
        //delete size_over_life_;
        //delete velocity_over_life_;
        //delete velocity_over_life_between_curves;
    }

    void ParticleSystemComponent::OnCreate(bool a_on_load) {

        color_over_lifetime_ui_state_ = 0;

        Vector2 velocityCurveValues[4];
        velocityCurveValues[0].setX(0);
        velocityCurveValues[0].setY(0);
        velocityCurveValues[1].setX(.1f);
        velocityCurveValues[1].setY(.0f);
        velocityCurveValues[2].setX(.9f);
        velocityCurveValues[2].setY(.0f);
        velocityCurveValues[3].setX(1.0f);
        velocityCurveValues[3].setY(.0f);
        if(!angular_velocity_over_life_){
            angular_velocity_over_life_ = std::make_shared<ImGui::IgaCurveEditor3>(velocityCurveValues);
        }
        if (!angular_velocity_over_life_between_curves) {
            angular_velocity_over_life_between_curves = std::make_shared<ImGui::IgaCurveEditor3Double>(velocityCurveValues);
            spawn_angular_velocity_ = Vector3(0.f);
            spawn_angular_velocity_random_ = Vector3(0.f);
        }
        if (color_over_life_r_ == nullptr) { 
            color_over_life_r_ = std::make_shared<ImGui::IgaCurveEditor>(velocityCurveValues); 
        };
        if (color_over_life_g_ == nullptr) { color_over_life_g_ = std::make_shared<ImGui::IgaCurveEditor>(velocityCurveValues); };
        if (color_over_life_b_ == nullptr) { color_over_life_b_ = std::make_shared<ImGui::IgaCurveEditor>(velocityCurveValues); };
        if (color_over_life_a_ == nullptr) { color_over_life_a_ = std::make_shared<ImGui::IgaCurveEditor>(velocityCurveValues); };

        if (a_on_load && texture_resource_) {
            SetTexture(texture_resource_->GetPath());
        }
        else {
            SetTexture("Default");
        }

        mesh_resource_ = GetResourceManager()->Load<Mesh>("Sprite");

        render_data_struct_vector_.reserve(max_particles_);
        particle_vector_.reserve(max_particles_);

        // Build render data
        for (size_t i = 0; i < max_particles_; i++) {
            GrowPoolCount();
        }
        particle_vector_[0].position_ += Vector3(0, -1, 0);
    }

    void ParticleSystemComponent::GrowPoolCount() {
        particle_vector_.push_back(Particle());

        render_data_struct_vector_.push_back(RenderDataStruct());
        int newIndex = render_data_struct_vector_.size() - 1;
        render_data_struct_vector_[newIndex].mesh_ = GetResourceManager()->Get<Mesh>(mesh_resource_);
        render_data_struct_vector_[newIndex].texture_ = GetResourceManager()->Get<Texture>(texture_resource_);

        render_data_struct_vector_[newIndex].model_matrix_ = Matrix4::identity();
        render_data_struct_vector_[newIndex].modifier_ = modifiers_;
    }

    void ParticleSystemComponent::Update() {

        if (following_other && !follow_object_alive)
        {
            loop_ = false;
            burst_time_ = 99999999.0f;
        }

        // spawn particle
        float delta_time_ = GetTime()->GetGameDeltaTime();
        if (loop_) {
            spawn_timer_ -= delta_time_;

            for (size_t i = 0; i < max_spawn_per_frame_; i++) {
                if (spawn_timer_ < 0.0f) {
                    SpawnParticle();
                    spawn_timer_ += spawn_time_;
                }
            }
        }
        // burst particle
        if (burst_) {
            if (burst_once_ && !burst_executed_) {
                Burst();
                burst_executed_ = true;
            }
            if (!burst_once_) {
                burst_timer_ -= delta_time_;
                if (burst_timer_ < 0.0f) {
                    burst_timer_ = burst_time_;
                    Burst();
                }
            }
        }

        // update particles
        for (size_t particle_index_ = 0; particle_index_ < particle_vector_.size(); particle_index_++) {
            if (particle_vector_[particle_index_].alive_) {
                particle_vector_[particle_index_].life_time_ -= delta_time_;
                float deltatimeScaled = delta_time_ * 60;
                if (particle_vector_[particle_index_].life_time_ < 0.0f) {
                    particle_vector_[particle_index_].alive_ = false;
                }
                float lifeScale = 1.0f - (particle_vector_[particle_index_].life_time_ / particle_vector_[particle_index_].total_live_time_);
                if (scaleMode == 1) {
                    particle_vector_[particle_index_].scale_ = start_scale_ + Vector3(size_over_life_->GetPoint(lifeScale).getY());
                }
                Vector3 velocity = Vector3(0.0f);
                Vector3 minVelocity = Vector3(0.0f);
                if (velocityMode == 1) {
                    velocity = Vector3(velocity_over_life_->GetPoint(lifeScale)) * deltatimeScaled;
                }
                else if (velocityMode == 2) {
                    velocity = velocity_over_life_between_curves->GetPoint(lifeScale, particle_vector_[particle_index_].random_velocity_between_curves_) * deltatimeScaled;
                }
                else {
                    velocity = particle_vector_[particle_index_].velocity_ * deltatimeScaled;
                }
                if (lengthSqr(velocity) != 0.0f)
                {
                    minVelocity = normalize(velocity) * minimum_velocity_magnitude_;
                }
                particle_vector_[particle_index_].position_ += velocity + minVelocity;
                if (angular_velocity_mode_ == 1) {
                    particle_vector_[particle_index_].angular_velocity_ = Vector3(angular_velocity_over_life_->GetPoint(lifeScale));
                }
                else if (angular_velocity_mode_ == 2) {
                    particle_vector_[particle_index_].angular_velocity_ =
                        angular_velocity_over_life_between_curves->GetPoint(lifeScale, particle_vector_[particle_index_].random_angular_velocity_between_curves_);
                }
                particle_vector_[particle_index_].rotation_ += particle_vector_[particle_index_].angular_velocity_ * deltatimeScaled;
                

                Vector4 colorOverLifeTime = Vector4(
                    color_over_life_r_->GetPoint(lifeScale).getY(),
                    color_over_life_g_->GetPoint(lifeScale).getY(),
                    color_over_life_b_->GetPoint(lifeScale).getY(),
                    color_over_life_a_->GetPoint(lifeScale).getY());
                render_data_struct_vector_[particle_index_].modifier_.color_ = mulPerElem(particle_vector_[particle_index_].color_, colorOverLifeTime);
            }
            if (!particle_vector_[particle_index_].alive_) {
                particle_vector_[particle_index_].scale_ = Vector3(0);
            }

            render_data_struct_vector_[particle_index_].model_matrix_ =
                Matrix4::translation(particle_vector_[particle_index_].position_) *
                Matrix4::rotationX(particle_vector_[particle_index_].rotation_.getX()) *
                Matrix4::rotationY(particle_vector_[particle_index_].rotation_.getY()) *
                Matrix4::rotationZ(particle_vector_[particle_index_].rotation_.getZ()) *
                Matrix4::scale(particle_vector_[particle_index_].scale_);
            render_data_struct_vector_[particle_index_].model_matrix_ *= Matrix4::scale(owner_.lock()->GetTransform().lock()->GetScale());
        }
    }

    void ParticleSystemComponent::SpawnParticle() {
        
        // check if can spawn
        int particlesAlive = 0;
        for (size_t particle_index_ = 0; particle_index_ < particle_vector_.size(); particle_index_++) {
            if (particle_vector_[particle_index_].alive_) {
                particlesAlive++;
            }
        }
        if (particlesAlive - 1 > max_particles_) {
            return;
        }

        // get spawn position
        Vector3 entity_spawn_position_;
        if (owner_.lock()->GetTransform().lock()->GetParent().lock()) {
            last_follow_position = owner_.lock()->GetTransform().lock()->GetParent().lock()->GetPosition();
            following_other = true;
            follow_object_alive = true;
            entity_spawn_position_ = last_follow_position;
        }
        else
        {
            follow_object_alive = false;
            if (following_other) {
                entity_spawn_position_ = last_follow_position;
            } 
            else if(owner_.lock()) {
                entity_spawn_position_ = owner_.lock()->GetTransform().lock()->GetPosition();
            }
        }

        for (size_t particle_index_ = 0; particle_index_ < particle_vector_.size(); particle_index_++) {
            // spawn
            if (!particle_vector_[particle_index_].alive_) {
                // start spawning particle
                particle_vector_[particle_index_].alive_ = true;
                float randFloat = RandomFloat();
                particle_vector_[particle_index_].life_time_ = particle_life_time_ + (particle_life_time_random_ * randFloat);
                particle_vector_[particle_index_].total_live_time_ = particle_vector_[particle_index_].life_time_;
                particle_vector_[particle_index_].velocity_ = spawn_velocity_ + (spawn_velocity_random_ * RandomFloat());
                particle_vector_[particle_index_].angular_velocity_ = spawn_angular_velocity_ + (spawn_angular_velocity_random_* RandomFloat());
                Vector3 rand_spawn_offset = mulPerElem(spawn_range_, Vector3(RandomFloat(), RandomFloat(), RandomFloat()));
                particle_vector_[particle_index_].position_ = entity_spawn_position_ + rand_spawn_offset + spawn_position_;
                particle_vector_[particle_index_].scale_ = start_scale_ + (start_scale_random_* RandomFloat());
                particle_vector_[particle_index_].random_velocity_between_curves_ = Vector3(RandomFloat(true), RandomFloat(true), RandomFloat(true));
                particle_vector_[particle_index_].random_angular_velocity_between_curves_ = Vector3(RandomFloat(true), RandomFloat(true), RandomFloat(true));

                particle_vector_[particle_index_].rotation_ = 
                    spawn_rotation_ + (mulPerElem(random_spawn_rotion_,Vector3(RandomFloat(), RandomFloat(), RandomFloat())));
                
                particle_vector_[particle_index_].color_ = start_color_ + (start_color_random_offset_ * RandomFloat(true));

                return;
            };
        }
        // no particle spawn because max particle count reached
        return;
    }

    void ParticleSystemComponent::Burst() {
        if (burst_count_ > max_spawn_per_frame_) {
            burst_count_ = max_spawn_per_frame_;
        }
        for (size_t i = 0; i < burst_count_; i++) {
            SpawnParticle();
        }
    }

    float ParticleSystemComponent::RandomFloat(bool a_from_0_to_1) {
        if(!a_from_0_to_1)return (((float)(rand() % 1000)) / 1000.0f) - .5f;
        return (((float)(rand() % 1000)) / 1000.0f);
    }

    void ParticleSystemComponent::PostUpdate() {

    }

    void ParticleSystemComponent::SetTexture(const std::string & a_path) {
        texture_resource_ = GetResourceManager()->Load<Texture>(a_path);

#ifdef EDITOR
        char buffer[256] = "";
        const std::string& name = a_path;
        int name_length = name.length();

        for (int i = 0; i < 256; ++i) {
            if (i < name_length) {
                texture_path_buffer_[i] = name.c_str()[i];
            }
            else {
                texture_path_buffer_[i] = buffer[i];
            }
        }
#endif
    }

    std::vector<RenderDataStruct>* ParticleSystemComponent::OnBuildRenderData() {
        if(visible_) {
#ifdef EDITOR
            if(!visible_editor_only_ || (visible_editor_only_ && !GetGame()->IsPlaying())) {
#endif
                for(size_t i = 0; i < max_particles_; i++) {
                    render_data_struct_vector_[i].modifier_.sprite_uv_offset_and_scale = modifiers_.sprite_uv_offset_and_scale;
                    render_data_struct_vector_[i].texture_ = GetResourceManager()->Get<Texture>(texture_resource_);
                }
                return &render_data_struct_vector_;
#ifdef EDITOR
            }
#endif
        }
        return nullptr;
    }

    void ParticleSystemComponent::Inspect() {
#ifdef EDITOR
        collapsing_header_open_ = false;
        collapsing_header_id_ = 0;
        if_one_collapsing_header_open_ = false;

        ImGuiTreeNodeFlags tree_node_flags_ = ImGuiTreeNodeFlags_Framed;

        if (ImGui::CollapsingHeader("Rendering", tree_node_flags_)) {
            ImGui::Text("Texture path");
            ImGuiInputTextFlags input_text_flags = ImGuiInputTextFlags_EnterReturnsTrue;

            ImGui::SameLine(0.f, 0.f);
            ImGui::Text(":");
            ImGui::SameLine(0.f, 10.f);

            if (ImGui::InputText("TexturePath", texture_path_buffer_, IM_ARRAYSIZE(texture_path_buffer_), input_text_flags)) {
                SetTexture(texture_path_buffer_);
            }
        }

        ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() * 0.4f);


        ImGui::PushID("PushID part PS spawning");
        StartCollapsingHeaderState();
        if (ImGui::CollapsingHeader("Spawn", tree_node_flags_)) {
            collapsing_header_open_ = true;
            if_one_collapsing_header_open_ = true;
            if (collapsing_header_state_last_frame_ == collapsing_header_id_)
            {
                int newParticleSize = max_particles_;
                ImGui::DragInt("max particles", &newParticleSize, 1, 1, 10000);

                if (newParticleSize > max_particles_)
                {
                    int grow = newParticleSize - max_particles_;
                    for (size_t i = 0; i < grow; i++)
                    {
                        GrowPoolCount();
                    }
                }
                max_particles_ = newParticleSize;

                ImGui::DragInt("Max spawn per frame", &max_spawn_per_frame_);
                ImGui::Checkbox("Loop", &loop_);
                ImGui::Checkbox("Burst", &burst_);
                ImGui::Checkbox("Burst once", &burst_once_);
                ImGui::DragFloat("Burst time", &burst_time_);
                ImGui::DragInt("Burst count", &burst_count_, 1.0f, 0, max_spawn_per_frame_);
                ImGui::DragFloat("Spawn time", &spawn_time_, 0.001f, 0.00001f, 2.0f);
                ImGui::DragFloat("Particle life time ", &particle_life_time_, 0.01f, 0.0001f, 100.0f);
                ImGui::DragFloat("Particle life time random", &particle_life_time_random_, 0.01f, 0.0001f, 100.0f);

                ImGui::DragVector3("Spawn position", spawn_position_, 0.01f, -10000.0f, 10000.0f);
                ImGui::DragVector3("Spawn range", spawn_range_, 0.01f, -10000.0f, 10000.0f);

                ImGui::DragVector3("Spawn rotation", spawn_rotation_, 0.01f, -10000.0f, 10000.0f);
                ImGui::DragVector3("Spawn rotation random", random_spawn_rotion_, 0.01f, -10000.0f, 10000.0f);

                ImGui::ColorEditv4("Spawn color", start_color_);
                ImGui::ColorEditv4("Spawn color random offset", start_color_random_offset_);
            }
        }
        EndCollapsingHeaderState();
        ImGui::PopID();
        
        ImGui::PushID("PushID part Color ol");
        StartCollapsingHeaderState();
        if (ImGui::CollapsingHeader("Color over lifetime", tree_node_flags_)) {
            collapsing_header_open_ = true;
            if_one_collapsing_header_open_ = true;
            if (collapsing_header_state_last_frame_ == collapsing_header_id_) {
                ImGui::RadioButton("R", &color_over_lifetime_ui_state_, 0); ImGui::SameLine();
                ImGui::RadioButton("G", &color_over_lifetime_ui_state_, 1); ImGui::SameLine();
                ImGui::RadioButton("B", &color_over_lifetime_ui_state_, 2); ImGui::SameLine();
                ImGui::RadioButton("A", &color_over_lifetime_ui_state_, 3);
                switch (color_over_lifetime_ui_state_)
                {
                case 0:
                    ImGui::PushID("01PushID pcol01");  color_over_life_r_->Inspect("Color over lifetime red"); ImGui::PopID();
                    break;
                case 1:
                    ImGui::PushID("02PushID pcol02");  color_over_life_g_->Inspect("Color over lifetime green"); ImGui::PopID();
                    break;
                case 2:
                    ImGui::PushID("03PushID pcol03");  color_over_life_b_->Inspect("Color over lifetime blue"); ImGui::PopID();
                    break;
                case 3:
                    ImGui::PushID("04PushID pcol04");  color_over_life_a_->Inspect("Color over lifetime alpha"); ImGui::PopID();
                    break;
                default:
                    break;
                }
            }
        }
        EndCollapsingHeaderState();
        ImGui::PopID();

        ImGui::PushID("PushID part Angular momentum");
        StartCollapsingHeaderState();
        if (ImGui::CollapsingHeader("Angular momentum", tree_node_flags_)) {
            collapsing_header_open_ = true;
            if_one_collapsing_header_open_ = true;
            if (collapsing_header_state_last_frame_ == collapsing_header_id_) {
                ImGui::RadioButton("Angular momentum mode: constant", &angular_velocity_mode_, 0);
                ImGui::RadioButton("Angular momentum mode: Angular momentum lifetime", &angular_velocity_mode_, 1);
                ImGui::RadioButton("Angular momentum mode: Angular momentum lifetime random between curves", &angular_velocity_mode_, 2);
                if (angular_velocity_mode_ == 0)
                {
                    ImGui::DragVector3("Angular velocity scale", spawn_angular_velocity_, 0.001f, 0.0f, 10000.0f);
                }
                ImGui::DragVector3("Angular velocity scale random offset", spawn_angular_velocity_random_, 0.001f, 0.0f, 10000.0f);
                if (angular_velocity_mode_ == 1)
                {
                    angular_velocity_over_life_->Inspect("Angular velocity over lifetime");
                }
                else if (angular_velocity_mode_ == 2)
                {
                    angular_velocity_over_life_between_curves->Inspect("angel V lerp curves");
                }
            }
        }
        EndCollapsingHeaderState();
        ImGui::PopID();

        ImGui::PushID("PushID part Velocity");
        StartCollapsingHeaderState();
        if (ImGui::CollapsingHeader("Velocity", tree_node_flags_)) {
            collapsing_header_open_ = true;
            if_one_collapsing_header_open_ = true;
            if (collapsing_header_state_last_frame_ == collapsing_header_id_) {
                //minVelocity
                ImGui::DragFloat("Minimum Velocity", &minimum_velocity_magnitude_,.001f,-100000.0f, 100000.0f);
                ImGui::RadioButton("Velocity mode: constant", &velocityMode, 0);
                ImGui::RadioButton("Velocity mode: velocity lifetime", &velocityMode, 1);
                ImGui::RadioButton("Velocity mode: velocity lifetime random between curves", &velocityMode, 2);
                if (velocityMode == 0)
                {
                    ImGui::DragVector3("Velocity scale", spawn_velocity_, 0.001f, 0.0f, 10000.0f);
                }
                ImGui::DragVector3("Velocity scale random offset", spawn_velocity_random_, 0.001f, 0.0f, 10000.0f);
                if (velocityMode == 1)
                {
                    velocity_over_life_->Inspect("velocity over lifetime");
                }
                else if (velocityMode == 2)
                {
                    velocity_over_life_between_curves->Inspect("V over L lerp curves");//velocity over lifetime random between 2 curves
                }
            }
        }
        EndCollapsingHeaderState();
        ImGui::PopID();

        ImGui::PushID("PushID part Scale momentum");
        StartCollapsingHeaderState();
        if (ImGui::CollapsingHeader("Scale", tree_node_flags_)) {
            collapsing_header_open_ = true;
            if_one_collapsing_header_open_ = true;
            if (collapsing_header_state_last_frame_ == collapsing_header_id_)
            {
                ImGui::RadioButton("scale mode: constant", &scaleMode, 0);
                ImGui::RadioButton("scale mode: size lifetime", &scaleMode, 1);
                if (scaleMode == 0)
                {
                    ImGui::DragVector3("start scale", start_scale_, 0.01f, 0.0f, 10000.0f);
                }
                ImGui::DragVector3("start scale random offset", start_scale_random_, 0.01f, 0.0f, 10000.0f);
                if (scaleMode == 1)
                {
                    size_over_life_->Inspect("size over lifetime");
                }
            }
        }
        EndCollapsingHeaderState();
        ImGui::PopID();

        if(!if_one_collapsing_header_open_)
        {
            collapsing_header_state_ = -1;
        }
        collapsing_header_state_last_frame_ = collapsing_header_state_;
#endif
    }

    void ParticleSystemComponent::StartCollapsingHeaderState()
    {
        collapsing_header_open_ = (collapsing_header_state_ == collapsing_header_id_) ? true : false;
#ifdef EDITOR
        bool open_for_one_frame_ = (collapsing_header_state_last_frame_ == collapsing_header_id_);
        ImGui::SetNextTreeNodeOpen((open_for_one_frame_&&collapsing_header_open_));
#endif
    }

    void ParticleSystemComponent::EndCollapsingHeaderState()
    {
        collapsing_header_state_ = collapsing_header_open_ ? collapsing_header_id_ : collapsing_header_state_;
        collapsing_header_id_++;
    }

}