1use std::f32::consts::PI;
11
12#[repr(C)]
20#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
21pub(crate) struct AxesVertex {
22 pub(crate) position: [f32; 2],
24 pub(crate) color: [f32; 4],
26}
27
28impl AxesVertex {
29 pub(crate) fn buffer_layout() -> wgpu::VertexBufferLayout<'static> {
31 wgpu::VertexBufferLayout {
32 array_stride: std::mem::size_of::<AxesVertex>() as wgpu::BufferAddress,
33 step_mode: wgpu::VertexStepMode::Vertex,
34 attributes: &[
35 wgpu::VertexAttribute {
36 offset: 0,
37 shader_location: 0,
38 format: wgpu::VertexFormat::Float32x2,
39 },
40 wgpu::VertexAttribute {
41 offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
42 shader_location: 1,
43 format: wgpu::VertexFormat::Float32x4,
44 },
45 ],
46 }
47 }
48}
49
50#[derive(Debug, Clone, Copy)]
56pub struct AxisView {
57 pub orientation: glam::Quat,
59 pub axis_index: usize,
61}
62
63const X_COLOR: [f32; 4] = [0.878, 0.322, 0.322, 1.0]; const Y_COLOR: [f32; 4] = [0.361, 0.722, 0.361, 1.0]; const Z_COLOR: [f32; 4] = [0.290, 0.620, 1.000, 1.0]; const ORIGIN_OFFSET: f32 = 40.0;
70const LINE_LENGTH: f32 = 30.0;
71const LINE_HALF_WIDTH: f32 = 1.0;
72const CIRCLE_RADIUS: f32 = 9.0;
73const CIRCLE_SEGMENTS: usize = 24;
74
75pub(crate) fn build_axes_geometry(
86 viewport_w: f32,
87 viewport_h: f32,
88 orientation: glam::Quat,
89) -> Vec<AxesVertex> {
90 let mut verts = Vec::with_capacity(1024);
91
92 let px_to_ndc_x = |px: f32| -> f32 { px * 2.0 / viewport_w };
94 let px_to_ndc_y = |py: f32| -> f32 { py * 2.0 / viewport_h };
95
96 let ox = -1.0 + px_to_ndc_x(ORIGIN_OFFSET);
98 let oy = -1.0 + px_to_ndc_y(ORIGIN_OFFSET);
99
100 let view_right = orientation * glam::Vec3::X;
102 let view_up = orientation * glam::Vec3::Y;
103 let view_fwd = orientation * glam::Vec3::Z; let project = |world_axis: glam::Vec3| -> (f32, f32) {
106 let sx = world_axis.dot(view_right);
107 let sy = world_axis.dot(view_up); (sx * px_to_ndc_x(LINE_LENGTH), sy * px_to_ndc_y(LINE_LENGTH))
109 };
110
111 let axes_world = [glam::Vec3::X, glam::Vec3::Y, glam::Vec3::Z];
112 let colors = [X_COLOR, Y_COLOR, Z_COLOR];
113 let offsets: [(f32, f32); 3] = [
114 project(glam::Vec3::X),
115 project(glam::Vec3::Y),
116 project(glam::Vec3::Z),
117 ];
118
119 let mut order: [usize; 3] = [0, 1, 2];
121 order.sort_by(|&a, &b| {
122 let da = axes_world[a].dot(view_fwd);
123 let db = axes_world[b].dot(view_fwd);
124 da.partial_cmp(&db).unwrap()
125 });
126
127 for &i in &order {
128 let (dx, dy) = offsets[i];
129 let color = colors[i];
130 let tip_x = ox + dx;
131 let tip_y = oy + dy;
132
133 let lw_x = px_to_ndc_x(LINE_HALF_WIDTH);
135 let lw_y = px_to_ndc_y(LINE_HALF_WIDTH);
136 let len = (dx * dx + dy * dy).sqrt().max(0.001);
138 let perp_x = -dy / len;
139 let perp_y = dx / len;
140 let px_ = perp_x * lw_x;
141 let py_ = perp_y * lw_y;
142
143 push_quad(
144 &mut verts,
145 [ox + px_, oy + py_],
146 [ox - px_, oy - py_],
147 [tip_x - px_, tip_y - py_],
148 [tip_x + px_, tip_y + py_],
149 color,
150 );
151
152 let bg_color = [color[0] * 0.33, color[1] * 0.33, color[2] * 0.33, 0.7];
154 let cr_x = px_to_ndc_x(CIRCLE_RADIUS);
155 let cr_y = px_to_ndc_y(CIRCLE_RADIUS);
156 push_circle_filled(&mut verts, tip_x, tip_y, cr_x, cr_y, bg_color);
157
158 let ring_inner = 0.82; push_circle_ring(&mut verts, tip_x, tip_y, cr_x, cr_y, ring_inner, color);
161
162 let glyph_hw = px_to_ndc_x(4.5); let glyph_hh = px_to_ndc_y(4.5); let glw_x = px_to_ndc_x(0.8); let glw_y = px_to_ndc_y(0.8);
167 match i {
168 0 => push_letter_x(
169 &mut verts, tip_x, tip_y, glyph_hw, glyph_hh, glw_x, glw_y, color,
170 ),
171 1 => push_letter_y(
172 &mut verts, tip_x, tip_y, glyph_hw, glyph_hh, glw_x, glw_y, color,
173 ),
174 2 => push_letter_z(
175 &mut verts, tip_x, tip_y, glyph_hw, glyph_hh, glw_x, glw_y, color,
176 ),
177 _ => {}
178 }
179 }
180
181 verts
182}
183
184pub fn hit_test(
193 screen_pos: [f32; 2],
194 viewport_rect: [f32; 4],
195 orientation: glam::Quat,
196) -> Option<AxisView> {
197 let vp_x = viewport_rect[0];
198 let vp_y = viewport_rect[1];
199 let vp_h = viewport_rect[3];
200
201 let rel_x = screen_pos[0] - vp_x;
203 let rel_y = vp_h - (screen_pos[1] - vp_y); let ox = ORIGIN_OFFSET;
207 let oy = ORIGIN_OFFSET;
208
209 let view_right = orientation * glam::Vec3::X;
211 let view_up = orientation * glam::Vec3::Y;
212 let view_fwd = orientation * glam::Vec3::Z; let project = |world_axis: glam::Vec3| -> (f32, f32) {
215 let sx = world_axis.dot(view_right);
216 let sy = world_axis.dot(view_up);
217 (ox + sx * LINE_LENGTH, oy + sy * LINE_LENGTH)
218 };
219
220 let axes = [glam::Vec3::X, glam::Vec3::Y, glam::Vec3::Z];
221 let frac_1_sqrt_2 = std::f32::consts::FRAC_1_SQRT_2;
226 let front = glam::Quat::from_xyzw(0.0, frac_1_sqrt_2, frac_1_sqrt_2, 0.0);
227 let targets = [
228 AxisView {
229 orientation: glam::Quat::from_rotation_z(-std::f32::consts::FRAC_PI_2) * front,
230 axis_index: 0,
231 }, AxisView {
233 orientation: front,
234 axis_index: 1,
235 }, AxisView {
237 orientation: glam::Quat::IDENTITY,
238 axis_index: 2,
239 }, ];
241
242 let mut order: [usize; 3] = [0, 1, 2];
244 order.sort_by(|&a, &b| {
245 let da = axes[a].dot(view_fwd);
246 let db = axes[b].dot(view_fwd);
247 db.partial_cmp(&da).unwrap() });
249
250 for &i in &order {
251 let (tx, ty) = project(axes[i]);
252 let dx = rel_x - tx;
253 let dy = rel_y - ty;
254 if dx * dx + dy * dy <= CIRCLE_RADIUS * CIRCLE_RADIUS {
255 return Some(targets[i]);
256 }
257 }
258
259 None
260}
261
262fn push_quad(
267 verts: &mut Vec<AxesVertex>,
268 a: [f32; 2],
269 b: [f32; 2],
270 c: [f32; 2],
271 d: [f32; 2],
272 color: [f32; 4],
273) {
274 for &pos in &[a, b, c, a, c, d] {
276 verts.push(AxesVertex {
277 position: pos,
278 color,
279 });
280 }
281}
282
283fn push_circle_filled(
284 verts: &mut Vec<AxesVertex>,
285 cx: f32,
286 cy: f32,
287 rx: f32,
288 ry: f32,
289 color: [f32; 4],
290) {
291 let step = 2.0 * PI / CIRCLE_SEGMENTS as f32;
292 for i in 0..CIRCLE_SEGMENTS {
293 let a0 = step * i as f32;
294 let a1 = step * (i + 1) as f32;
295 verts.push(AxesVertex {
296 position: [cx, cy],
297 color,
298 });
299 verts.push(AxesVertex {
300 position: [cx + rx * a0.cos(), cy + ry * a0.sin()],
301 color,
302 });
303 verts.push(AxesVertex {
304 position: [cx + rx * a1.cos(), cy + ry * a1.sin()],
305 color,
306 });
307 }
308}
309
310fn push_circle_ring(
311 verts: &mut Vec<AxesVertex>,
312 cx: f32,
313 cy: f32,
314 rx: f32,
315 ry: f32,
316 inner_frac: f32,
317 color: [f32; 4],
318) {
319 let step = 2.0 * PI / CIRCLE_SEGMENTS as f32;
320 let irx = rx * inner_frac;
321 let iry = ry * inner_frac;
322 for i in 0..CIRCLE_SEGMENTS {
323 let a0 = step * i as f32;
324 let a1 = step * (i + 1) as f32;
325 let (c0, s0) = (a0.cos(), a0.sin());
326 let (c1, s1) = (a1.cos(), a1.sin());
327 let o0 = [cx + rx * c0, cy + ry * s0];
328 let o1 = [cx + rx * c1, cy + ry * s1];
329 let i0 = [cx + irx * c0, cy + iry * s0];
330 let i1 = [cx + irx * c1, cy + iry * s1];
331 for &pos in &[o0, i0, o1, o1, i0, i1] {
333 verts.push(AxesVertex {
334 position: pos,
335 color,
336 });
337 }
338 }
339}
340
341fn push_letter_x(
343 verts: &mut Vec<AxesVertex>,
344 cx: f32,
345 cy: f32,
346 hw: f32,
347 hh: f32,
348 lw_x: f32,
349 lw_y: f32,
350 color: [f32; 4],
351) {
352 push_line_segment(verts, cx - hw, cy + hh, cx + hw, cy - hh, lw_x, lw_y, color);
354 push_line_segment(verts, cx - hw, cy - hh, cx + hw, cy + hh, lw_x, lw_y, color);
356}
357
358fn push_letter_y(
360 verts: &mut Vec<AxesVertex>,
361 cx: f32,
362 cy: f32,
363 hw: f32,
364 hh: f32,
365 lw_x: f32,
366 lw_y: f32,
367 color: [f32; 4],
368) {
369 push_line_segment(verts, cx - hw, cy + hh, cx, cy, lw_x, lw_y, color);
371 push_line_segment(verts, cx + hw, cy + hh, cx, cy, lw_x, lw_y, color);
373 push_line_segment(verts, cx, cy, cx, cy - hh, lw_x, lw_y, color);
375}
376
377fn push_letter_z(
379 verts: &mut Vec<AxesVertex>,
380 cx: f32,
381 cy: f32,
382 hw: f32,
383 hh: f32,
384 lw_x: f32,
385 lw_y: f32,
386 color: [f32; 4],
387) {
388 push_line_segment(verts, cx - hw, cy + hh, cx + hw, cy + hh, lw_x, lw_y, color);
390 push_line_segment(verts, cx + hw, cy + hh, cx - hw, cy - hh, lw_x, lw_y, color);
392 push_line_segment(verts, cx - hw, cy - hh, cx + hw, cy - hh, lw_x, lw_y, color);
394}
395
396fn push_line_segment(
398 verts: &mut Vec<AxesVertex>,
399 x0: f32,
400 y0: f32,
401 x1: f32,
402 y1: f32,
403 lw_x: f32,
404 lw_y: f32,
405 color: [f32; 4],
406) {
407 let dx = x1 - x0;
408 let dy = y1 - y0;
409 let len = (dx * dx + dy * dy).sqrt().max(0.0001);
410 let px = -(dy / len) * lw_x;
412 let py = (dx / len) * lw_y;
413
414 push_quad(
415 verts,
416 [x0 + px, y0 + py],
417 [x0 - px, y0 - py],
418 [x1 - px, y1 - py],
419 [x1 + px, y1 + py],
420 color,
421 );
422}