ParticleEditorUi.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. // Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
  2. // All rights reserved.
  3. // Code licensed under the BSD License.
  4. // http://www.anki3d.org/LICENSE
  5. #include <AnKi/Editor/ParticleEditorUi.h>
  6. #include <AnKi/Resource/ResourceFilesystem.h>
  7. #include <AnKi/Resource/ResourceManager.h>
  8. #include <AnKi/Util/Filesystem.h>
  9. #include <ThirdParty/ImGui/Extra/IconsMaterialDesignIcons.h> // See all icons in https://pictogrammers.com/library/mdi/
  10. namespace anki {
  11. void ParticleEditorUi::open(const ParticleEmitterResource2& resource)
  12. {
  13. if(m_programs.getSize() == 0)
  14. {
  15. gatherParticlePrograms();
  16. }
  17. rebuildCache(resource);
  18. m_filename = ResourceFilesystem::getSingleton().getFileFullPath(resource.getFilename());
  19. m_open = true;
  20. }
  21. void ParticleEditorUi::drawWindow([[maybe_unused]] UiCanvas& canvas, Vec2 initialPos, Vec2 initialSize, ImGuiWindowFlags windowFlags)
  22. {
  23. if(!m_open)
  24. {
  25. return;
  26. }
  27. if(m_programs.getSize() == 0)
  28. {
  29. gatherParticlePrograms();
  30. }
  31. if(ImGui::GetFrameCount() > 1)
  32. {
  33. ImGui::SetNextWindowPos(initialPos, ImGuiCond_FirstUseEver);
  34. ImGui::SetNextWindowSize(initialSize, ImGuiCond_FirstUseEver);
  35. }
  36. // Do the window now
  37. Bool cacheDirty = false;
  38. if(ImGui::Begin("Particle Editor", &m_open, windowFlags))
  39. {
  40. if(ImGui::BeginChild("Toolbox", Vec2(0.0f), ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY,
  41. ImGuiWindowFlags_None))
  42. {
  43. if(ImGui::Button(ICON_MDI_CONTENT_SAVE " Save"))
  44. {
  45. if(saveCache())
  46. {
  47. ANKI_LOGE("Unnable to save the particles file. Ignoring save");
  48. }
  49. ResourceManager::getSingleton().refreshFileUpdateTimes();
  50. }
  51. ImGui::SameLine();
  52. ImGui::SetNextItemWidth(-1.0f);
  53. ImGui::Separator();
  54. ImGui::EndChild();
  55. }
  56. if(ImGui::BeginChild("Content", Vec2(-1.0f, -1.0f), ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeX, windowFlags))
  57. {
  58. ImGui::SeparatorText("Common Properties");
  59. // <shaderProgram>
  60. if(ImGui::BeginCombo("Program", (m_currentlySelectedProgram.getLength()) ? m_currentlySelectedProgram.cstr() : nullptr,
  61. ImGuiComboFlags_HeightLarge))
  62. {
  63. for(U32 i = 0; i < m_programs.getSize(); ++i)
  64. {
  65. const Bool isSelected = (m_programs[i].m_name == m_currentlySelectedProgram);
  66. if(ImGui::Selectable(m_programs[i].m_name.cstr(), isSelected))
  67. {
  68. cacheDirty = cacheDirty || m_currentlySelectedProgram != m_programs[i].m_name;
  69. m_currentlySelectedProgram = m_programs[i].m_name;
  70. }
  71. if(isSelected)
  72. {
  73. ImGui::SetItemDefaultFocus();
  74. }
  75. }
  76. ImGui::EndCombo();
  77. }
  78. // <particleCount>
  79. I32 particleCount = I32(m_commonProps.m_particleCount);
  80. if(ImGui::InputInt("Particle Count", &particleCount))
  81. {
  82. m_commonProps.m_particleCount = U32(max(0, particleCount));
  83. }
  84. // <emissionPeriod>
  85. if(ImGui::InputFloat("Emission Period", &m_commonProps.m_emissionPeriod, 0.01f))
  86. {
  87. m_commonProps.m_emissionPeriod = max(0.0f, m_commonProps.m_emissionPeriod);
  88. }
  89. // <particlesPerEmission>
  90. I32 particlesPerEmission = I32(m_commonProps.m_particlesPerEmission);
  91. if(ImGui::InputInt("Particles Per Emission", &particlesPerEmission))
  92. {
  93. m_commonProps.m_particlesPerEmission = U32(clamp(particlesPerEmission, 0, particleCount));
  94. }
  95. ImGui::SeparatorText("Other Properties");
  96. // Other props
  97. for(Prop& prop : m_otherProps)
  98. {
  99. [[maybe_unused]] Bool valueChanged = false;
  100. switch(prop.m_type)
  101. {
  102. case ShaderVariableDataType::kU32:
  103. valueChanged = ImGui::InputInt(prop.m_name.cstr(), &prop.m_I32);
  104. prop.m_I32 = max(prop.m_I32, 0);
  105. break;
  106. case ShaderVariableDataType::kUVec2:
  107. valueChanged = ImGui::InputInt2(prop.m_name.cstr(), &prop.m_I32);
  108. prop.m_IVec2 = prop.m_IVec2.max(0);
  109. break;
  110. case ShaderVariableDataType::kUVec3:
  111. valueChanged = ImGui::InputInt3(prop.m_name.cstr(), &prop.m_I32);
  112. prop.m_IVec3 = prop.m_IVec3.max(0);
  113. break;
  114. case ShaderVariableDataType::kUVec4:
  115. valueChanged = ImGui::InputInt4(prop.m_name.cstr(), &prop.m_I32);
  116. prop.m_IVec4 = prop.m_IVec4.max(0);
  117. break;
  118. case ShaderVariableDataType::kF32:
  119. valueChanged = ImGui::InputFloat(prop.m_name.cstr(), &prop.m_F32, 1.0f);
  120. break;
  121. case ShaderVariableDataType::kVec2:
  122. valueChanged = ImGui::InputFloat2(prop.m_name.cstr(), &prop.m_F32);
  123. break;
  124. case ShaderVariableDataType::kVec3:
  125. valueChanged = ImGui::InputFloat3(prop.m_name.cstr(), &prop.m_F32);
  126. break;
  127. case ShaderVariableDataType::kVec4:
  128. valueChanged = ImGui::InputFloat4(prop.m_name.cstr(), &prop.m_F32);
  129. break;
  130. default:
  131. ANKI_ASSERT(!"TODO");
  132. }
  133. }
  134. }
  135. ImGui::EndChild();
  136. }
  137. ImGui::End();
  138. if(cacheDirty)
  139. {
  140. rebuildCache(m_currentlySelectedProgram);
  141. }
  142. }
  143. void ParticleEditorUi::gatherParticlePrograms()
  144. {
  145. ResourceFilesystem::getSingleton().iterateAllFilenames([this](CString filename) {
  146. String ext;
  147. getFilepathExtension(filename, ext);
  148. const CString extension = "ankiprogbin";
  149. if(ext == extension)
  150. {
  151. ResourceFilePtr file;
  152. ShaderBinary* binary = nullptr;
  153. if(ResourceFilesystem::getSingleton().openFile(filename, file)
  154. || deserializeShaderBinaryFromAnyFile(*file, binary, DefaultMemoryPool::getSingleton()))
  155. {
  156. ANKI_LOGE("Failed to load particles file. Ignoring this file: %s", filename.cstr());
  157. }
  158. else
  159. {
  160. for(const auto& strct : binary->m_structs)
  161. {
  162. if(CString(strct.m_name.getBegin()).find("AnKiParticleEmitterProperties") == 0)
  163. {
  164. String basename;
  165. getFilepathFilename(filename, basename);
  166. ANKI_ASSERT(basename.getLength() > extension.getLength() + 1);
  167. basename = String(basename.getBegin(), basename.getBegin() + basename.getLength() - extension.getLength() - 1);
  168. ParticleProgram& prog = *m_programs.emplaceBack();
  169. prog.m_filename = filename;
  170. prog.m_name = basename;
  171. prog.m_props.resize(strct.m_members.getSize());
  172. for(U32 i = 0; i < prog.m_props.getSize(); ++i)
  173. {
  174. prog.m_props[i].m_name = strct.m_members[i].m_name.getBegin();
  175. prog.m_props[i].m_type = strct.m_members[i].m_type;
  176. switch(prog.m_props[i].m_type)
  177. {
  178. #define ANKI_SVDT_MACRO(type, baseType, rowCount, columnCount, isIntagralType) \
  179. case ShaderVariableDataType::k##type: \
  180. { \
  181. memcpy(&prog.m_props[i].m_F32, strct.m_members[i].m_defaultValues.getBegin(), sizeof(type)); \
  182. break; \
  183. }
  184. #include <AnKi/Gr/ShaderVariableDataType.def.h>
  185. default:
  186. ANKI_ASSERT(0);
  187. break;
  188. }
  189. }
  190. }
  191. }
  192. DefaultMemoryPool::getSingleton().free(binary);
  193. }
  194. }
  195. return FunctorContinue::kContinue;
  196. });
  197. }
  198. void ParticleEditorUi::rebuildCache(const ParticleEmitterResource2& rsrc)
  199. {
  200. m_commonProps = rsrc.getCommonProperties();
  201. m_currentlySelectedProgram = rsrc.getShaderProgramResource().getFilename();
  202. getFilepathFilename(m_currentlySelectedProgram, m_currentlySelectedProgram);
  203. const CString extension = "ankiprogbin";
  204. m_currentlySelectedProgram = String(m_currentlySelectedProgram.getBegin(),
  205. m_currentlySelectedProgram.getBegin() + m_currentlySelectedProgram.getLength() - (extension.getLength() + 1));
  206. m_otherProps.resize(rsrc.getOtherProperties().getSize());
  207. for(U32 i = 0; i < m_otherProps.getSize(); ++i)
  208. {
  209. m_otherProps[i].m_name = rsrc.getOtherProperties()[i].getName();
  210. m_otherProps[i].m_type = rsrc.getOtherProperties()[i].getDataType();
  211. switch(m_otherProps[i].m_type)
  212. {
  213. #define ANKI_SVDT_MACRO(type, baseType, rowCount, columnCount, isIntagralType) \
  214. case ShaderVariableDataType::k##type: \
  215. { \
  216. m_otherProps[i].m_##type = rsrc.getOtherProperties()[i].getValue<type>(); \
  217. break; \
  218. }
  219. #include <AnKi/Gr/ShaderVariableDataType.def.h>
  220. default:
  221. ANKI_ASSERT(0);
  222. break;
  223. }
  224. }
  225. }
  226. void ParticleEditorUi::rebuildCache(CString particleProgramName)
  227. {
  228. const ParticleProgram* foundProg = nullptr;
  229. for(const ParticleProgram& prog : m_programs)
  230. {
  231. if(particleProgramName == prog.m_name)
  232. {
  233. foundProg = &prog;
  234. break;
  235. }
  236. }
  237. ANKI_ASSERT(foundProg);
  238. m_commonProps = {};
  239. m_currentlySelectedProgram = particleProgramName;
  240. m_otherProps.resize(foundProg->m_props.getSize());
  241. for(U32 i = 0; i < m_otherProps.getSize(); ++i)
  242. {
  243. m_otherProps[i].m_name = foundProg->m_props[i].m_name;
  244. m_otherProps[i].m_type = foundProg->m_props[i].m_type;
  245. m_otherProps[i].m_Mat4 = foundProg->m_props[i].m_Mat4;
  246. }
  247. }
  248. Error ParticleEditorUi::saveCache()
  249. {
  250. File file;
  251. ANKI_CHECK(file.open(m_filename, FileOpenFlag::kWrite));
  252. ANKI_CHECK(file.writeText("<particleEmitter>\n"));
  253. ANKI_CHECK(file.writeTextf("\t<shaderProgram name=\"%s\"/>\n", m_currentlySelectedProgram.cstr()));
  254. ANKI_CHECK(file.writeTextf("\t<particleCount value=\"%u\"/>\n", m_commonProps.m_particleCount));
  255. ANKI_CHECK(file.writeTextf("\t<emissionPeriod value=\"%f\"/>\n", m_commonProps.m_emissionPeriod));
  256. ANKI_CHECK(file.writeTextf("\t<particlesPerEmission value=\"%u\"/>\n", m_commonProps.m_particlesPerEmission));
  257. ANKI_CHECK(file.writeText("\t<inputs>\n"));
  258. for(const Prop& prop : m_otherProps)
  259. {
  260. String value;
  261. switch(prop.m_type)
  262. {
  263. case ShaderVariableDataType::kU32:
  264. value.toString(prop.m_U32);
  265. break;
  266. case ShaderVariableDataType::kUVec2:
  267. value = prop.m_UVec2.toString();
  268. break;
  269. case ShaderVariableDataType::kUVec3:
  270. value = prop.m_UVec3.toString();
  271. break;
  272. case ShaderVariableDataType::kUVec4:
  273. value = prop.m_UVec4.toString();
  274. break;
  275. case ShaderVariableDataType::kF32:
  276. value.toString(prop.m_F32);
  277. break;
  278. case ShaderVariableDataType::kVec2:
  279. value = prop.m_Vec2.toString();
  280. break;
  281. case ShaderVariableDataType::kVec3:
  282. value = prop.m_Vec3.toString();
  283. break;
  284. case ShaderVariableDataType::kVec4:
  285. value = prop.m_Vec4.toString();
  286. break;
  287. default:
  288. ANKI_ASSERT(!"TODO");
  289. }
  290. ANKI_CHECK(file.writeTextf("\t\t<input name=\"%s\" value=\"%s\"/>\n", prop.m_name.cstr(), value.cstr()));
  291. }
  292. ANKI_CHECK(file.writeText("\t</inputs>\n"));
  293. ANKI_CHECK(file.writeText("</particleEmitter>\n"));
  294. return Error::kNone;
  295. }
  296. } // end namespace anki