diff --git a/.gitignore b/.gitignore index 710c7fa2..52119d54 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,7 @@ ext/imgui/docs/ ext/imgui/examples/ ext/imgui/misc/ ext/imgui/.* + +# CodeQL +_codeql_build_dir/ +_codeql_detected_source_root diff --git a/CMakeLists.txt b/CMakeLists.txt index 2609747c..087dafc4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,6 +110,7 @@ set(COMP_FILES src/components/ramachandran/ramachandran.cpp src/components/shapespace/shapespace.cpp src/components/dataset/dataset.cpp + src/components/particlesystem/particlesystem.cpp ) if (VIAMD_ENABLE_VELOXCHEM) @@ -138,6 +139,10 @@ set(SHADER_FILES src/shaders/volume/raycaster.frag src/shaders/ssao/ssao.frag src/shaders/ssao/blur.frag + src/shaders/particlesystem/seed_particles.comp + src/shaders/particlesystem/advect_particles.comp + src/shaders/particlesystem/render_particles.vert + src/shaders/particlesystem/render_particles.frag ) # Bake shaders into a single header file diff --git a/src/components/particlesystem/particlesystem.cpp b/src/components/particlesystem/particlesystem.cpp new file mode 100644 index 00000000..d19ca657 --- /dev/null +++ b/src/components/particlesystem/particlesystem.cpp @@ -0,0 +1,380 @@ +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +namespace particlesystem { + +struct ParticleParams { + mat4_t texture_to_world; + mat4_t world_to_texture; + float dt; + float scalar_min; + float grad_magn_min; + float min_lifetime_in_frames; + float max_lifetime_in_frames; + uint32_t num_particles; + uint32_t seed; + uint32_t max_attempts; +}; + +struct RenderParams { + mat4_t model_view_proj; + vec4_t particle_color; + float particle_size; + float max_lifetime_in_frames; + uint32_t _pad[2]; +}; + +struct ParticleSystem : viamd::EventHandler { + bool show_window = false; + bool enabled = false; + bool initialized = false; + + // Particle parameters + uint32_t num_particles = 10000; + float min_lifetime_in_frames = 100.0f; + float max_lifetime_in_frames = 1000.0f; + float scalar_min = 0.02f; + float grad_magn_min = 0.01f; + float timestep = 10.0f; + float particle_size = 2.0f; + vec4_t particle_color = {1.0f, 1.0f, 1.0f, 0.8f}; + uint32_t max_attempts = 64; + + // OpenGL resources + GLuint particle_buffer = 0; + GLuint params_ubo = 0; + GLuint render_ubo = 0; + GLuint render_vao = 0; + + GLuint advect_program = 0; + GLuint render_program = 0; + + // Volume texture reference + GLuint volume_texture = 0; + int volume_dim[3] = {0, 0, 0}; + mat4_t volume_texture_to_world = mat4_ident(); + mat4_t volume_world_to_texture = mat4_ident(); + + // Frame counter for seed diversity + uint32_t frame_counter = 0; + + ParticleSystem() { + viamd::event_system_register_handler(*this); + } + + virtual ~ParticleSystem() { + cleanup_gl_resources(); + } + + void initialize_gl_resources() { + // Compile compute shader + GLuint advect_shader = gl::compile_shader_from_source( + {(const char*)advect_particles_comp, advect_particles_comp_size}, + GL_COMPUTE_SHADER + ); + + // Create compute program + advect_program = glCreateProgram(); + if (!gl::attach_link_detach(advect_program, &advect_shader, 1)) { + glDeleteProgram(advect_program); + advect_program = 0; + } + glDeleteShader(advect_shader); + + // Compile render shaders + GLuint vert_shader = gl::compile_shader_from_source( {(const char*)render_particles_vert, render_particles_vert_size}, GL_VERTEX_SHADER ); + GLuint frag_shader = gl::compile_shader_from_source( {(const char*)render_particles_frag, render_particles_frag_size}, GL_FRAGMENT_SHADER ); + + // Create render program + render_program = glCreateProgram(); + { + const GLuint shaders[] = { vert_shader, frag_shader }; + if (!gl::attach_link_detach(render_program, shaders, 2)) { + glDeleteProgram(render_program); + render_program = 0; + } + } + glDeleteShader(vert_shader); + glDeleteShader(frag_shader); + + // Create uniform buffer (shared by seed and advect) + glGenBuffers(1, ¶ms_ubo); + glBindBuffer(GL_UNIFORM_BUFFER, params_ubo); + glBufferData(GL_UNIFORM_BUFFER, sizeof(ParticleParams), nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + + glGenBuffers(1, &render_ubo); + glBindBuffer(GL_UNIFORM_BUFFER, render_ubo); + glBufferData(GL_UNIFORM_BUFFER, sizeof(RenderParams), nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + + // Create empty VAO (required by OpenGL, even though we use SSBO) + glGenVertexArrays(1, &render_vao); + } + + void cleanup_gl_resources() { + if (particle_buffer) glDeleteBuffers(1, &particle_buffer); + if (params_ubo) glDeleteBuffers(1, ¶ms_ubo); + if (render_ubo) glDeleteBuffers(1, &render_ubo); + if (render_vao) glDeleteVertexArrays(1, &render_vao); + if (advect_program) glDeleteProgram(advect_program); + if (render_program) glDeleteProgram(render_program); + } + + void initialize_particles() { + if (initialized && particle_buffer) { + glDeleteBuffers(1, &particle_buffer); + } + + // Create particle buffer with zero-initialized data + // All particles start with life = 0, so they'll be reseeded on first update + std::vector initial_particles(num_particles, vec4_t{0, 0, 0, 0}); + + glGenBuffers(1, &particle_buffer); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, particle_buffer); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(vec4_t) * num_particles, initial_particles.data(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + initialized = true; + } + + void update_particles(float dt) { + if (!initialized || !volume_texture || !advect_program) return; + + PUSH_GPU_SECTION("Update Particle System") + + // Increment frame counter for seed diversity + frame_counter++; + + ParticleParams params = {}; + params.texture_to_world = volume_texture_to_world; + params.world_to_texture = volume_world_to_texture; + params.dt = dt; + params.scalar_min = scalar_min; + params.grad_magn_min = grad_magn_min; + params.min_lifetime_in_frames = min_lifetime_in_frames; + params.max_lifetime_in_frames = max_lifetime_in_frames; + params.num_particles = num_particles; + params.seed = (frame_counter * 1664525u + 1013904223u); + params.max_attempts = max_attempts; + + glBindBuffer(GL_UNIFORM_BUFFER, params_ubo); + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(ParticleParams), ¶ms); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + + glUseProgram(advect_program); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, particle_buffer); + glBindBufferBase(GL_UNIFORM_BUFFER, 0, params_ubo); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_3D, volume_texture); + + uint32_t group_count = (num_particles + 255) / 256; + glDispatchCompute(group_count, 1, 1); + glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); + + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0); + glBindBufferBase(GL_UNIFORM_BUFFER, 0, 0); + glUseProgram(0); + + POP_GPU_SECTION() + } + + void render_particles(const mat4_t& view_proj_matrix) { + if (!initialized || !render_program) return; + + // Debug: Check if we have particles + if (num_particles == 0) { + MD_LOG_DEBUG("Particle system: num_particles is 0"); + return; + } + + PUSH_GPU_SECTION("Draw Particle System") + + RenderParams params = {}; + params.model_view_proj = view_proj_matrix; + params.particle_color = particle_color; + params.particle_size = particle_size; + params.max_lifetime_in_frames = max_lifetime_in_frames; + + glBindBuffer(GL_UNIFORM_BUFFER, render_ubo); + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(RenderParams), ¶ms); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + + // Save and set OpenGL state + GLboolean depth_test_enabled = glIsEnabled(GL_DEPTH_TEST); + GLboolean depth_mask; + glGetBooleanv(GL_DEPTH_WRITEMASK, &depth_mask); + + glEnable(GL_DEPTH_TEST); // Don't depth test particles + glDepthMask(GL_FALSE); // Don't write to depth buffer + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_PROGRAM_POINT_SIZE); + + glUseProgram(render_program); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, particle_buffer); + glBindBufferBase(GL_UNIFORM_BUFFER, 0, render_ubo); + + glBindVertexArray(render_vao); + glDrawArrays(GL_POINTS, 0, num_particles); + glBindVertexArray(0); + + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0); + glBindBufferBase(GL_UNIFORM_BUFFER, 0, 0); + glUseProgram(0); + + // Restore OpenGL state + glDisable(GL_PROGRAM_POINT_SIZE); + glDisable(GL_BLEND); + if (depth_test_enabled) glEnable(GL_DEPTH_TEST); + glDepthMask(depth_mask); + + POP_GPU_SECTION() + } + + void draw_ui(const ApplicationState& app_state) { + if (!show_window) return; + + if (ImGui::Begin("Particle System", &show_window)) { + if (ImGui::Checkbox("Enabled", &enabled)) { + if (enabled && !initialized) { + initialize_particles(); + } + } + + ImGui::Separator(); + ImGui::Text("Particle Parameters"); + + // @TODO: Enlist all representations which have a volume texture and allow user to select one + size_t num_representations = md_array_size(app_state.representation.reps); + + const int cap = 16; + const char* volume_names[cap] = { "None" }; + int volume_rep_indices[cap] = { -1 }; + int len = 1; + + for (size_t i = 0; i < num_representations && i < cap - 1; ++i) { + const Representation& rep = app_state.representation.reps[i]; + if (rep.electronic_structure.vol.format == VolumeFormat::R16G16B16A16_FLOAT || + rep.electronic_structure.vol.format == VolumeFormat::R32G32B32A32_FLOAT) { + volume_names[len] = app_state.representation.reps[i].name; + volume_rep_indices[len] = (int)i; + len++; + } + } + + static int vol_index = 0; + if (ImGui::Combo("Volume Texture", &vol_index, volume_names, len)) { + if (vol_index != 0) { + int rep_idx = volume_rep_indices[vol_index]; + const Representation& rep = app_state.representation.reps[rep_idx]; + volume_texture = rep.electronic_structure.vol.tex_id; + volume_dim[0] = rep.electronic_structure.vol.dim[0]; + volume_dim[1] = rep.electronic_structure.vol.dim[1]; + volume_dim[2] = rep.electronic_structure.vol.dim[2]; + volume_texture_to_world = rep.electronic_structure.vol.texture_to_world; + volume_world_to_texture = mat4_inverse(volume_texture_to_world); + } else { + volume_texture = 0; + volume_dim[0] = volume_dim[1] = volume_dim[2] = 0; + volume_texture_to_world = mat4_ident(); + volume_world_to_texture = mat4_ident(); + } + // Initialize particles when volume changes + initialize_particles(); + } + + bool params_changed = false; + params_changed |= ImGui::SliderInt("Num Particles", (int*)&num_particles, 1000, 1000000); + ImGui::RangeSliderFloat("Lifetime Range", &min_lifetime_in_frames, &max_lifetime_in_frames, 100.0f, 10000.0f); + ImGui::SliderFloat("Scalar Min", &scalar_min, 0.0f, 0.2f, "%.7f"); + ImGui::SliderFloat("Gradient Magnitude Min", &grad_magn_min, 0.0f, 0.2f, "%.7f"); + ImGui::SliderInt("Max Attempts", (int*)&max_attempts, 1, 256); + + if (params_changed) { + initialize_particles(); + } + + ImGui::Separator(); + ImGui::Text("Rendering"); + ImGui::SliderFloat("Particle Size", &particle_size, 1.0f, 20.0f); + ImGui::ColorEdit4("Particle Color", &particle_color.x); + + ImGui::Separator(); + ImGui::Text("Simulation"); + ImGui::SliderFloat("Timestep", ×tep, -1.0f, 1.0f); + if (ImGui::Button("Reinitialize")) { + initialize_particles(); + } + } + ImGui::End(); + } + + void process_events(const viamd::Event* events, size_t num_events) override { + for (size_t i = 0; i < num_events; ++i) { + const viamd::Event& event = events[i]; + + switch (event.type) { + case viamd::EventType_ViamdInitialize: { + // Component initialization + initialize_gl_resources(); + break; + } + + case viamd::EventType_ViamdShutdown: { + cleanup_gl_resources(); + break; + } + + case viamd::EventType_ViamdFrameTick: { + ASSERT(event.payload_type == viamd::EventPayloadType_ApplicationState); + ApplicationState* app_state = (ApplicationState*)event.payload; + if (enabled && initialized) { + update_particles(timestep); + } + if (show_window) { + draw_ui(*app_state); + } + break; + } + + case viamd::EventType_ViamdRenderTransparent: { + if (enabled && initialized && event.payload_type == viamd::EventPayloadType_ApplicationState) { + auto* app_state = (ApplicationState*)event.payload; + if (app_state) { + // Compute view and projection matrices from camera + mat4_t view_proj = app_state->view.param.matrix.curr.proj * app_state->view.param.matrix.curr.view; + render_particles(view_proj); + } + } + break; + } + + case viamd::EventType_ViamdWindowDrawMenu: { + if (ImGui::BeginMenu("Components")) { + ImGui::MenuItem("Particle System", nullptr, &show_window); + ImGui::EndMenu(); + } + break; + } + + default: + break; + } + } + } +}; + +static ParticleSystem instance = {}; + +} // namespace particlesystem diff --git a/src/gfx/gl_utils.cpp b/src/gfx/gl_utils.cpp index faa3ecaa..e5327083 100644 --- a/src/gfx/gl_utils.cpp +++ b/src/gfx/gl_utils.cpp @@ -264,7 +264,7 @@ bool gl::init_texture_2D(GLuint* texture, int width, int height, GLenum format) bool gl::init_texture_3D(GLuint* texture, int width, int height, int depth, GLenum format) { ASSERT(texture); - ASSERT(format == GL_R32F || format == GL_R16F || format == GL_R8); + ASSERT(format == GL_R32F || format == GL_R16F || format == GL_R8 || format == GL_RGBA32F || format == GL_RGBA16F || format == GL_RGBA8); if (glIsTexture(*texture)) { int x, y, z; diff --git a/src/shaders/particlesystem/advect_particles.comp b/src/shaders/particlesystem/advect_particles.comp new file mode 100644 index 00000000..03e2cba4 --- /dev/null +++ b/src/shaders/particlesystem/advect_particles.comp @@ -0,0 +1,114 @@ +#version 430 core + +#ifndef GROUP_SIZE +#define GROUP_SIZE 256 +#endif + +layout (local_size_x = GROUP_SIZE) in; + +layout (std430, binding = 0) buffer particle_buffer { + vec4 particles[]; +}; + +layout (std140, binding = 0) uniform particle_params { + // Packed to respect std140 alignment and simplify C++ mirroring + mat4 texture_to_world; + mat4 world_to_texture; + float dt; + float scalar_min; + float grad_magn_min; + float min_lifetime_in_frames; + float max_lifetime_in_frames; + uint num_particles; + uint random_seed; + uint max_attempts; +}; + +layout (binding = 0) uniform sampler3D volume_texture; + +// PCG random number generator for reseeding +uint pcg_hash(uint seed) { + uint state = seed * 747796405u + 2891336453u; + uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; + return (word >> 22u) ^ word; +} + +float random_float(inout uint seed) { + seed = pcg_hash(seed); + return float(seed) / 4294967296.0; +} + +// Sample vector field at given position +vec3 sample_vector_field(vec3 world_pos, mat4 world_to_texture) { + // Convert world position to texture coordinates [0, 1] + vec4 tex_coord_h = world_to_texture * vec4(world_pos, 1.0); + vec3 tex_coord = tex_coord_h.xyz / tex_coord_h.w; + + // Sample the vector field (yzw components contain the gradient) + vec4 sample_value = texture(volume_texture, tex_coord); + + return -sample_value.yzw; +} + +void main() { + uint idx = gl_GlobalInvocationID.x; + if (idx >= num_particles) return; + + // Get current particle state + vec3 pos = particles[idx].xyz; + float life = particles[idx].w; + + // Reseed dead particles + if (life <= 0.0) { + // Combine frame counter (random_seed) with particle index for unique seed per reseed + uint seed = pcg_hash(random_seed + idx); + + // Try to find valid position within scalar range + for (int attempt = 0; attempt < max_attempts; ++attempt) { + // Generate random position in texture space [0,1]^3 + vec3 tex_pos = vec3( + random_float(seed), + random_float(seed), + random_float(seed) + ); + + // Transform to world space + pos = (texture_to_world * vec4(tex_pos, 1.0)).xyz; + + // Sample scalar value at texture position + vec4 sample_value = texture(volume_texture, tex_pos); + float scalar_value = sample_value.x; + float grad_magn = length(sample_value.yzw); + + if (scalar_value >= scalar_min && grad_magn >= grad_magn_min) { + break; + } + } + + life = min_lifetime_in_frames + random_float(seed) * (max_lifetime_in_frames - min_lifetime_in_frames); + particles[idx] = vec4(pos, life); + } + + // Advect particle using RK4 integration + vec3 k1 = sample_vector_field(pos, world_to_texture); + vec3 k2 = sample_vector_field(pos + k1 * dt * 0.5, world_to_texture); + vec3 k3 = sample_vector_field(pos + k2 * dt * 0.5, world_to_texture); + vec3 k4 = sample_vector_field(pos + k3 * dt, world_to_texture); + + vec3 new_vel = (k1 + 2.0 * k2 + 2.0 * k3 + k4) / 6.0; + vec3 new_pos = pos + new_vel * dt; + + // Check bounds: transform to texture space and see if in [0,1]^3 + vec4 tex_pos_h = world_to_texture * vec4(new_pos, 1.0); + vec3 tex_pos = tex_pos_h.xyz / tex_pos_h.w; + + if (any(lessThan(tex_pos, vec3(0.0))) || any(greaterThan(tex_pos, vec3(1.0)))) { + // Kill particle if it escapes - will be reseeded next frame + life = 0.0; + } else { + life = life - 1.0; + } + + // Update particle + particles[idx] = vec4(new_pos, life); +} diff --git a/src/shaders/particlesystem/render_particles.frag b/src/shaders/particlesystem/render_particles.frag new file mode 100644 index 00000000..00df584f --- /dev/null +++ b/src/shaders/particlesystem/render_particles.frag @@ -0,0 +1,28 @@ +#version 430 core + +in VS_OUT { + vec4 color; + float life; +} fs_in; + +layout (location = 0) out vec4 out_color; + +void main() { + + if (fs_in.life <= 0.0) + discard; + + // Create a smooth circular point sprite + //vec2 coord = gl_PointCoord - vec2(0.5); + //float dist = length(coord); + + //if (dist > 0.5) { + // discard; + //} + + // Smooth falloff at edges + //float alpha_falloff = 1.0 - smoothstep(0.3, 0.5, dist); + float alpha = fs_in.color.a; // * alpha_falloff; + + out_color = vec4(fs_in.color.rgb, alpha); +} diff --git a/src/shaders/particlesystem/render_particles.vert b/src/shaders/particlesystem/render_particles.vert new file mode 100644 index 00000000..1609b39e --- /dev/null +++ b/src/shaders/particlesystem/render_particles.vert @@ -0,0 +1,32 @@ +#version 430 core + +layout (std430, binding = 0) readonly buffer particle_buffer { + vec4 particles[]; +}; + +layout (std140, binding = 0) uniform render_params { + mat4 model_view_proj; + vec4 particle_color; + float particle_size; + float max_lifetime; + uint _pad[2]; +}; + +out VS_OUT { + vec4 color; + float life; +} vs_out; + +void main() { + vec4 particle = particles[gl_VertexID]; + + // Normalize lifetime to [0, 1] based on max_lifetime + // Particle.w stores remaining lifetime, so higher value = younger + float alpha = clamp(particle.w / max_lifetime, 0.0, 1.0); + + + gl_Position = model_view_proj * vec4(particle.xyz, 1.0); + gl_PointSize = particle_size; + vs_out.color = vec4(particle_color.rgb, particle_color.a * alpha); + vs_out.life = particle.w; +} diff --git a/src/shaders/particlesystem/seed_particles.comp b/src/shaders/particlesystem/seed_particles.comp new file mode 100644 index 00000000..f709cdad --- /dev/null +++ b/src/shaders/particlesystem/seed_particles.comp @@ -0,0 +1,90 @@ +#version 430 core + +#ifndef GROUP_SIZE +#define GROUP_SIZE 256 +#endif + +layout (local_size_x = GROUP_SIZE) in; + +layout (std430, binding = 0) buffer particle_buffer { + vec4 particles[]; +}; + +layout (std140, binding = 0) uniform particle_params { + uvec3 volume_dim; // Volume dimensions + uint num_particles; // Number of particles to seed + vec3 volume_min; // Volume AABB min + float dt; // Unused by seeding + vec3 volume_max; // Volume AABB max + float scalar_min; // Scalar threshold min + float scalar_max; // Scalar threshold max + float min_lifetime; // Minimum particle lifetime + float max_lifetime; // Maximum particle lifetime + uint random_seed; // Random seed + uint force_seed; // Force reseeding all particles +}; + +layout (binding = 0) uniform sampler3D volume_texture; + +// Simple random number generator (PCG) +uint pcg_hash(uint seed) { + uint state = seed * 747796405u + 2891336453u; + uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; + return (word >> 22u) ^ word; +} + +float random_float(inout uint seed) { + seed = pcg_hash(seed); + return float(seed) / 4294967296.0; +} + +void main() { + uint idx = gl_GlobalInvocationID.x; + if (idx >= num_particles) return; + + vec4 particle = particles[idx]; + + vec3 pos = particle.xyz; + float life = particle.w; + + if (force_seed == 1) { + life = -1.0; + } + + if (life > 0.0) { + // Particle is still alive, no need to reseed + return; + } + + // Initialize random seed with global index and user seed + uint seed = random_seed + idx * 1664525u + 1013904223u; + + // Try multiple times to find a valid position within scalar range + const int max_attempts = 32; + float scalar_value = 0.0; + + for (int attempt = 0; attempt < max_attempts; ++attempt) { + // Generate random position within volume bounds + pos = vec3( + volume_min.x + random_float(seed) * (volume_max.x - volume_min.x), + volume_min.y + random_float(seed) * (volume_max.y - volume_min.y), + volume_min.z + random_float(seed) * (volume_max.z - volume_min.z) + ); + + // Convert to texture coordinates [0, 1] + vec3 tex_coord = (pos - volume_min) / (volume_max - volume_min); + + // Sample the scalar value (in x component) + vec4 sample_value = texture(volume_texture, tex_coord); + scalar_value = sample_value.x; + + // Break if within desired range + if (scalar_value >= scalar_min && scalar_value <= scalar_max) { + break; + } + } + + // Generate random lifetime + float lifetime = min_lifetime + random_float(seed) * (max_lifetime - min_lifetime); + particles[idx] = vec4(pos, lifetime); +} diff --git a/src/viamd.h b/src/viamd.h index a78c572c..675c9280 100644 --- a/src/viamd.h +++ b/src/viamd.h @@ -237,25 +237,25 @@ struct DatasetItem { md_array(int) sub_items = 0; // Indices into the items of the subcatagories: i.e. for chain -> unique residues types within that chain }; -struct DipoleMoment { +struct DipoleMomentInfo { size_t num_dipoles = 0; str_t* label = nullptr; vec3_t* vec = nullptr; }; -struct NaturalTransitionOrbitalLambda { +struct NaturalTransitionOrbitalLambdaInfo { size_t num_lambdas = 0; str_t* label = nullptr; double* value = nullptr; }; -struct NaturalTransitionOrbital { +struct NaturalTransitionOrbitalInfo { size_t num_orbitals = 0; str_t* label = nullptr; - NaturalTransitionOrbitalLambda* lambda = nullptr; + NaturalTransitionOrbitalLambdaInfo* lambda = nullptr; }; -struct MolecularOrbital { +struct MolecularOrbitalInfo { size_t homo_idx = 0; size_t lumo_idx = 0; size_t num_orbitals = 0; @@ -264,7 +264,7 @@ struct MolecularOrbital { double* energy = nullptr; }; -struct AtomProperty { +struct AtomPropertyInfo { uint64_t id; str_t label; int num_idx = 0; @@ -272,28 +272,44 @@ struct AtomProperty { float value_max = 0; }; -// Struct to fill in for the different components -// Which provides information of what representations are available for the currently loaded datasets -struct RepresentationInfo { - MolecularOrbital alpha; - MolecularOrbital beta; - NaturalTransitionOrbital nto; - DipoleMoment electric_dipoles; - DipoleMoment magnetic_dipoles; - DipoleMoment velocity_dipoles; +struct ElectronicStructureInfo { + MolecularOrbitalInfo alpha; + MolecularOrbitalInfo beta; + NaturalTransitionOrbitalInfo nto; + DipoleMomentInfo electric_dipoles; + DipoleMomentInfo magnetic_dipoles; + DipoleMomentInfo velocity_dipoles; uint32_t electronic_structure_type_mask; +}; - md_array(AtomProperty) atom_properties = nullptr; +struct AtomicPropertyInfo { + md_array(AtomPropertyInfo) properties = 0; +}; + +// Struct to fill in for the different components +// Which provides information of what representations are available for the currently loaded datasets +struct RepresentationInfo { + ElectronicStructureInfo electronic_structure; + AtomicPropertyInfo atomic_property; md_allocator_i* alloc = nullptr; }; +enum class VolumeFormat { + R16_FLOAT, + R16G16_FLOAT, + R16G16B16A16_FLOAT, + R32_FLOAT, + R32G32_FLOAT, + R32G32B32A32_FLOAT, +}; + struct Volume { - //mat4_t index_to_world = {}; mat4_t texture_to_world = {}; vec3_t voxel_size = {1,1,1}; // Size of each voxel in world units int dim[3] = {128, 128, 128}; + VolumeFormat format = VolumeFormat::R16_FLOAT; uint32_t tex_id = 0; }; @@ -305,7 +321,7 @@ struct IsoDesc { vec4_t colors[8]; }; -struct EvalAtomProperty { +struct AtomicPropertyPayload { uint64_t property_id = 0; int idx = 0; // This is probably rarely applicable @@ -315,12 +331,13 @@ struct EvalAtomProperty { }; // Event Payload when an electronic structure is to be evaluated -struct EvalElectronicStructure { +struct ElectronicStructurePayload { // Input information ElectronicStructureType type = ElectronicStructureType::MolecularOrbital; int major_idx = 0; int minor_idx = 0; float samples_per_angstrom = 4.0f; + bool include_gradients = false; // Output information bool output_written = false;