#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_++;
}
}