--- a/plugins/cube/cube.cpp +++ b/plugins/cube/cube.cpp @@ -412,7 +412,7 @@ wf_cube_animation_attribs animation{runtime_duration}; // Keep added state local: resident Cube libraries export the original // animation attributes, whose layout must remain compatible on reload. - timed_transition_t row_rotation{animation.cube_animation}; + timed_transition_t row_translation{animation.cube_animation}; void start_animation(int duration_limit_ms = -1) { @@ -469,17 +469,6 @@ return output->wset()->get_workspace_grid_size().width; } - static float row_side_angle(int rows) - { - // Rows span at most half a turn, like latitudes from pole to pole. - return M_PI / std::max(2, rows - 1); - } - - static float row_z_offset(int rows) - { - return 0.5 / std::tan(row_side_angle(rows) / 2); - } - bool refresh_cube_geometry() { auto grid = output->wset()->get_workspace_grid_size(); @@ -489,7 +478,7 @@ return false; } - float old_offset = vertical_transition ? row_z_offset(geometry_rows) : identity_z_offset; + float old_offset = identity_z_offset; animation.side_angle = 2 * M_PI / float(faces); identity_z_offset = faces == 1 ? 0.0f : 0.5 / std::tan(animation.side_angle / 2); @@ -497,8 +486,7 @@ { // Preserve the camera's distance from the cube when a new // workspace count changes its radius. - float new_offset = vertical_transition ? row_z_offset(grid.height) : identity_z_offset; - float offset = animation.cube_animation.offset_z + new_offset - old_offset; + float offset = animation.cube_animation.offset_z + identity_z_offset - old_offset; animation.cube_animation.offset_z.set(offset, offset); } @@ -512,7 +500,7 @@ if (vertical_transition) { vertical_transition = false; - row_rotation.set(0, 0); + row_translation.set(0, 0); animation.cube_animation.offset_z.set(identity_z_offset + Z_OFFSET_NEAR, identity_z_offset + Z_OFFSET_NEAR); rebuild_cube_render_node(); @@ -678,12 +666,13 @@ fixed_view = nullptr; } - const float previous_pitch = vertical_transition ? - float(row_rotation) : 0.0f; + const float previous_offset = vertical_transition ? + float(row_translation) : 0.0f; vertical_transition = delta.y != 0; - const int rows = output->wset()->get_workspace_grid_size().height; - row_rotation.set( - vertical_transition ? previous_pitch + delta.y * row_side_angle(rows) : 0.0f, 0.0f); + // Rows are stacked upright. Preserve the visible position when a + // second key press reverses or continues an unfinished transition. + row_translation.set( + vertical_transition ? previous_offset + delta.y : 0.0f, 0.0f); workspace_animation_anchor = anchor_workspace; fixed_workspace_view = fixed_view; @@ -692,8 +681,8 @@ animation.in_exit = false; animation.cube_animation.zoom.set(1.0, 1.0); - const float radius = vertical_transition ? row_z_offset(rows) : identity_z_offset; - animation.cube_animation.offset_z.set(radius + Z_OFFSET_NEAR, radius + Z_OFFSET_NEAR); + animation.cube_animation.offset_z.set(identity_z_offset + Z_OFFSET_NEAR, + identity_z_offset + Z_OFFSET_NEAR); animation.cube_animation.offset_y.set(0, 0); animation.cube_animation.ease_deformation.set(0, 0); @@ -1026,7 +1015,7 @@ fixed_workspace_view = nullptr; workspace_animation_anchor.reset(); vertical_transition = false; - row_rotation.set(0, 0); + row_translation.set(0, 0); clear_fixed_workspace_overlay(); clear_unused_fixed_top_node(); release_cube_input(); @@ -1259,21 +1248,12 @@ static const GLuint indexData[] = {0, 1, 2, 0, 2, 3}; auto cws = output->wset()->get_current_workspace(); - const int count = vertical_transition ? (int)buffers.size() : get_num_faces(); + const int count = get_num_faces(); for (int i = 0; i < count; i++) { - int index = vertical_transition ? i : (cws.x + i) % get_num_faces(); + int index = (cws.x + i) % get_num_faces(); GL_CALL(glBindTexture(GL_TEXTURE_2D, wf::gles_texture_t::from_aux(buffers[index]).tex_id)); - auto model = calculate_model_matrix(i); - if (vertical_transition) - { - const int rows = output->wset()->get_workspace_grid_size().height; - const float angle = -((i - cws.y) * row_side_angle(rows) + - row_rotation); - model = glm::rotate(glm::mat4(1.0), angle, glm::vec3(1, 0, 0)) * - glm::translate(glm::mat4(1.0), glm::vec3(0, 0, row_z_offset(rows))); - } - program.uniformMatrix4f("model", model); + program.uniformMatrix4f("model", calculate_model_matrix(i)); if (tessellation_support) { @@ -1288,8 +1268,42 @@ } } + void render_vertical_rows(const wf::scene::render_instruction_t& data, + std::vector& buffers) + { + const auto size = wf::dimensions(output->get_relative_geometry()); + const int current_row = output->wset()->get_current_workspace().y; + // Round the shared offset once so adjacent rows meet without gaps. + const int offset = std::lround(float(row_translation) * size.height); + data.pass->custom_gles_subpass([&] + { + wf::gles::for_each_scissor_rect(data.target, data.damage, [&] + { + for (int i = 0; i < (int)buffers.size(); i++) + { + const int y = (i - current_row) * size.height + offset; + if ((y >= size.height) || (y + size.height <= 0)) + { + continue; + } + + // Translate full-size workspace textures. No pitch, face + // rotation, perspective scaling, or row wrapping. + OpenGL::render_texture(wf::gles_texture_t::from_aux(buffers[i]), + data.target, {0.0, double(y), double(size.width), double(size.height)}); + } + }); + }); + } + void render(const wf::scene::render_instruction_t& data, std::vector& buffers) { + if (vertical_transition) + { + render_vertical_rows(data, buffers); + return; + } + data.pass->custom_gles_subpass([&] { if (program.get_program_id(wf::TEXTURE_TYPE_RGBA) == 0)