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
|
--- a/plugins/cube/cube.cpp
+++ b/plugins/cube/cube.cpp
@@ -407,6 +407,7 @@
float identity_z_offset = 0.0f;
int geometry_faces = 0;
int geometry_rows = 0;
+ float geometry_aspect = 1.0f;
bool vertical_transition = false;
bool mouse_overview_active = false;
@@ -485,19 +486,43 @@
(get_num_faces() == 2 ? 1e-3f : 0.0f) : Z_OFFSET_NEAR;
}
+ float output_aspect_ratio()
+ {
+ const auto geometry = output->get_relative_geometry();
+ return std::max(1.0f, float(geometry.width)) /
+ std::max(1.0f, float(geometry.height));
+ }
+
+ float cube_front_extent(float yaw)
+ {
+ float extent = 0;
+ for (int face = 0; face < get_num_faces(); face++)
+ {
+ const float angle = face * animation.side_angle + yaw;
+ extent = std::max(extent, identity_z_offset * std::cos(angle) +
+ geometry_aspect * 0.5f * std::abs(std::sin(angle)));
+ }
+
+ return extent;
+ }
+
bool refresh_cube_geometry()
{
auto grid = output->wset()->get_workspace_grid_size();
int faces = grid.width;
- if ((faces == geometry_faces) && (grid.height == geometry_rows))
+ const float aspect = output_aspect_ratio();
+ if ((faces == geometry_faces) && (grid.height == geometry_rows) &&
+ (aspect == geometry_aspect))
{
return false;
}
float old_offset = identity_z_offset;
+ geometry_aspect = aspect;
+ animation.projection = glm::perspective(45.0f, aspect, 0.1f, 100.f);
animation.side_angle = 2 * M_PI / float(faces);
identity_z_offset = faces == 1 ? 0.0f :
- 0.5 / std::tan(animation.side_angle / 2);
+ 0.5f * aspect / std::tan(animation.side_angle / 2);
if (geometry_faces)
{
// Preserve the camera's distance from the cube when a new
@@ -668,7 +693,7 @@
#endif
}
- animation.projection = glm::perspective(45.0f, 1.f, 0.1f, 100.f);
+ animation.projection = glm::perspective(45.0f, output_aspect_ratio(), 0.1f, 100.f);
}
wf::signal::connection_t<cube_control_signal> on_cube_control = [=] (cube_control_signal *d)
@@ -1345,10 +1370,25 @@
}
/* Update the view matrix used in the next frame */
- void update_view_matrix()
- {
+ glm::mat4 cube_view_matrix(float zoom, bool nominal_camera = false)
+ {
+ const float pitch = animation.cube_animation.offset_y;
+ const bool stack = (mouse_overview_active || render_at_rest) && !vertical_transition;
+ const float rows = stack ? output->wset()->get_workspace_grid_size().height : 1;
+ const float row = mouse_overview_active ? float(overview_row) :
+ output->wset()->get_current_workspace().y;
+ const float center = stack ? ((rows - 1) / 2 - row) / zoom : 0;
+ // Keep the nearest corner in front of the camera as the regular
+ // polygon turns. Anchor zoom to that surface, rather than scaling
+ // a wide cube through a stationary camera.
+ const float support = std::cos(pitch) *
+ cube_front_extent(animation.cube_animation.rotation) +
+ std::abs(std::sin(pitch)) * rows * 0.5f;
+ const float base = nominal_camera ? identity_z_offset + front_face_distance() :
+ float(animation.cube_animation.offset_z);
+ const float distance = base + support / zoom - identity_z_offset;
auto zoom_translate = glm::translate(glm::mat4(1.f),
- glm::vec3(0.f, 0.f, -animation.cube_animation.offset_z));
+ glm::vec3(0.f, 0.f, -distance));
auto rotation = glm::rotate(glm::mat4(1.0),
(float)animation.cube_animation.offset_y,
@@ -1358,24 +1398,44 @@
glm::vec3(0., 0., -animation.cube_animation.offset_z),
glm::vec3(0., 1., 0.));
- animation.view = zoom_translate * rotation * view;
- if ((mouse_overview_active || render_at_rest) && !vertical_transition)
- {
- const float pitch = animation.cube_animation.offset_y;
- const float zoom = animation.cube_animation.zoom;
- const float rows = output->wset()->get_workspace_grid_size().height;
- const float row = mouse_overview_active ? float(overview_row) :
- output->wset()->get_current_workspace().y;
- const float center = ((rows - 1) / 2 - row) / zoom;
- auto pivot = glm::translate(glm::mat4(1.0), glm::vec3(0, center, 0));
- auto unpivot = glm::translate(glm::mat4(1.0), glm::vec3(0, -center, 0));
- // Orbit the complete stack about its middle. Move the camera back
- // as its height turns into depth, keeping the near faces visible.
- const float distance = float(animation.cube_animation.offset_z) +
- std::abs(std::sin(pitch)) * rows / (2 * zoom);
- zoom_translate = glm::translate(glm::mat4(1.0), glm::vec3(0, 0, -distance));
- animation.view = zoom_translate * pivot * rotation * unpivot * view;
- }
+ auto pivot = glm::translate(glm::mat4(1.0), glm::vec3(0, center, 0));
+ auto unpivot = glm::translate(glm::mat4(1.0), glm::vec3(0, -center, 0));
+ return zoom_translate * pivot * rotation * unpivot * view;
+ }
+
+ void update_view_matrix()
+ {
+ animation.view = cube_view_matrix(animation.cube_animation.zoom);
+ }
+
+ float overview_fit()
+ {
+ // Measure the projected outline with neutral wheel zoom and camera
+ // distance. Fitting model dimensions again would double-count the
+ // camera's pullback, making tilted wide desktops unnecessarily tiny.
+ // User zoom and camera distance remain free to change the fitted size.
+ auto vp = animation.projection * cube_view_matrix(1.0f, true);
+ const float last = output->wset()->get_workspace_grid_size().height - 1;
+ float extent = 1.0f;
+ for (float row : {0.0f, last})
+ {
+ for (int face = 0; face < get_num_faces(); face++)
+ {
+ auto matrix = vp * calculate_model_matrix(face, row - float(overview_row));
+ for (float x : {-0.5f, 0.5f})
+ {
+ for (float y : {-0.5f, 0.5f})
+ {
+ auto corner = matrix * glm::vec4(x, y, 0, 1);
+ const float w = std::max(1e-3f, corner.w);
+ extent = std::max(extent, std::max(std::abs(corner.x),
+ std::abs(corner.y)) / w);
+ }
+ }
+ }
+ }
+
+ return 0.8f / extent;
}
glm::mat4 output_transform(const wf::render_target_t& target)
@@ -1394,10 +1454,7 @@
{
// Fit every row while keeping the selected row centered. Scale
// the complete projected stack uniformly, preserving face shape.
- float center = overview_row;
- float last = output->wset()->get_workspace_grid_size().height - 1;
- float extent = 2 * std::max(center, last - center) + 1;
- float fit = 0.8f / std::max(1.0f, extent);
+ float fit = overview_fit();
float scale = 1 + float(overview_mix) * (fit - 1);
auto frame = glm::scale(glm::mat4(1.0), glm::vec3(scale, scale, 1));
// Overview rows share one camera and horizontal axis. Their
@@ -1428,7 +1485,11 @@
auto translation = glm::translate(glm::mat4(1.0),
glm::vec3(0, row_offset, identity_z_offset + additional_z));
- return rotation * translation;
+ // One workspace is aspect units wide and one unit tall. Its regular
+ // polygon radius uses the same width, so four faces form a square
+ // footprint without stretching the desktop textures.
+ auto face_size = glm::scale(glm::mat4(1.0), glm::vec3(geometry_aspect, 1, 1));
+ return rotation * translation * face_size;
}
/* Render the sides of the cube, using the given culling mode - cw or ccw */
|