1use super::gizmo_interact::{perpendiculars, GizmoAxis, GizmoBasis};
21use super::state::GizmoMode;
22use crate::math::{Mat4, Vec3};
23use crate::renderer::api::resource::view::ViewInfo;
24
25const GIZMO_SCREEN_FRACTION: f32 = 0.32;
32
33const RING_SEGMENTS: u32 = 48;
36
37#[repr(C)]
41#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
42pub struct GizmoLineInstance {
43 pub start: [f32; 4],
45 pub end: [f32; 4],
47 pub color: [f32; 4],
49}
50
51impl GizmoLineInstance {
52 pub fn new(start: Vec3, end: Vec3, color: [f32; 4]) -> Self {
54 Self {
55 start: [start.x, start.y, start.z, 1.0],
56 end: [end.x, end.y, end.z, 1.0],
57 color,
58 }
59 }
60}
61
62pub fn wireframe_cube(
64 transform: &Mat4,
65 half_extents: Vec3,
66 color: [f32; 4],
67) -> Vec<GizmoLineInstance> {
68 let corners = [
69 Vec3::new(-half_extents.x, -half_extents.y, -half_extents.z),
70 Vec3::new(half_extents.x, -half_extents.y, -half_extents.z),
71 Vec3::new(half_extents.x, half_extents.y, -half_extents.z),
72 Vec3::new(-half_extents.x, half_extents.y, -half_extents.z),
73 Vec3::new(-half_extents.x, -half_extents.y, half_extents.z),
74 Vec3::new(half_extents.x, -half_extents.y, half_extents.z),
75 Vec3::new(half_extents.x, half_extents.y, half_extents.z),
76 Vec3::new(-half_extents.x, half_extents.y, half_extents.z),
77 ];
78
79 let edges = [
80 (0, 1),
81 (1, 2),
82 (2, 3),
83 (3, 0),
84 (4, 5),
85 (5, 6),
86 (6, 7),
87 (7, 4),
88 (0, 4),
89 (1, 5),
90 (2, 6),
91 (3, 7),
92 ];
93
94 edges
95 .iter()
96 .map(|&(a, b)| {
97 let start = transform.transform_point(corners[a]);
98 let end = transform.transform_point(corners[b]);
99 GizmoLineInstance::new(start, end, color)
100 })
101 .collect()
102}
103
104pub fn transform_axes(transform: &Mat4, length: f32) -> Vec<GizmoLineInstance> {
109 let origin = transform.cols[3].truncate();
110
111 [Vec3::X, Vec3::Y, Vec3::Z]
112 .into_iter()
113 .zip(GizmoAxis::ALL)
114 .map(|(local, axis)| {
115 let direction = transform.transform_vector(local).normalize();
116 GizmoLineInstance::new(origin, origin + direction * length, axis.color())
117 })
118 .collect()
119}
120
121pub fn gizmo_world_size(view: &ViewInfo, origin: Vec3) -> f32 {
128 let focal = view.projection_matrix.cols[1].y;
130 if focal.abs() < 1e-6 {
131 return 1.0;
132 }
133 let distance = (origin - view.camera_position).length().max(1e-3);
134 distance / focal * GIZMO_SCREEN_FRACTION
135}
136
137pub fn manipulator(
142 pivot: Vec3,
143 basis: &GizmoBasis,
144 mode: GizmoMode,
145 size: f32,
146) -> Vec<GizmoLineInstance> {
147 match mode {
148 GizmoMode::Select => Vec::new(),
149 GizmoMode::Move => move_handles(pivot, basis, size),
150 GizmoMode::Rotate => rotate_handles(pivot, basis, size),
151 GizmoMode::Scale => scale_handles(pivot, basis, size),
152 }
153}
154
155fn move_handles(pivot: Vec3, basis: &GizmoBasis, size: f32) -> Vec<GizmoLineInstance> {
157 let head = size * 0.18;
158 let mut lines = Vec::with_capacity(GizmoAxis::ALL.len() * 5);
159
160 for axis in GizmoAxis::ALL {
161 let direction = basis[axis.index()];
162 let color = axis.color();
163 let tip = pivot + direction * size;
164 let base = tip - direction * head;
165 let (u, v) = perpendiculars(direction);
166
167 lines.push(GizmoLineInstance::new(pivot, tip, color));
168 for barb in [u, -u, v, -v] {
169 lines.push(GizmoLineInstance::new(
170 tip,
171 base + barb * (head * 0.4),
172 color,
173 ));
174 }
175 }
176 lines
177}
178
179fn scale_handles(pivot: Vec3, basis: &GizmoBasis, size: f32) -> Vec<GizmoLineInstance> {
182 let half = size * 0.09;
183 let mut lines = Vec::with_capacity(GizmoAxis::ALL.len() * 13);
184
185 for axis in GizmoAxis::ALL {
186 let direction = basis[axis.index()];
187 let color = axis.color();
188 let tip = pivot + direction * size;
189 let (u, v) = perpendiculars(direction);
190
191 lines.push(GizmoLineInstance::new(pivot, tip, color));
192
193 let corner = |i: usize| {
196 let sign = |bit: usize| if i & (1 << bit) == 0 { -1.0 } else { 1.0 };
197 tip + direction * (half * sign(0)) + u * (half * sign(1)) + v * (half * sign(2))
198 };
199 for i in 0..8usize {
201 for bit in 0..3 {
202 let j = i | (1 << bit);
203 if j != i {
204 lines.push(GizmoLineInstance::new(corner(i), corner(j), color));
205 }
206 }
207 }
208 }
209 lines
210}
211
212fn rotate_handles(pivot: Vec3, basis: &GizmoBasis, size: f32) -> Vec<GizmoLineInstance> {
214 let mut lines = Vec::with_capacity(GizmoAxis::ALL.len() * RING_SEGMENTS as usize);
215
216 for axis in GizmoAxis::ALL {
217 let direction = basis[axis.index()];
218 let color = axis.color();
219 let (u, v) = perpendiculars(direction);
220
221 for segment in 0..RING_SEGMENTS {
222 let theta = |s: u32| std::f32::consts::TAU * (s as f32 / RING_SEGMENTS as f32);
223 let point = |s: u32| {
224 let t = theta(s);
225 pivot + u * (size * t.cos()) + v * (size * t.sin())
226 };
227 lines.push(GizmoLineInstance::new(
228 point(segment),
229 point(segment + 1),
230 color,
231 ));
232 }
233 }
234 lines
235}
236
237pub fn camera_frustum(
239 transform: &Mat4,
240 fov_y: f32,
241 aspect: f32,
242 near: f32,
243 far: f32,
244 color: [f32; 4],
245) -> Vec<GizmoLineInstance> {
246 let half_h_near = (fov_y * 0.5).tan() * near;
247 let half_w_near = half_h_near * aspect;
248 let half_h_far = (fov_y * 0.5).tan() * far;
249 let half_w_far = half_h_far * aspect;
250
251 let near_corners = [
252 Vec3::new(-half_w_near, -half_h_near, -near),
253 Vec3::new(half_w_near, -half_h_near, -near),
254 Vec3::new(half_w_near, half_h_near, -near),
255 Vec3::new(-half_w_near, half_h_near, -near),
256 ];
257 let far_corners = [
258 Vec3::new(-half_w_far, -half_h_far, -far),
259 Vec3::new(half_w_far, -half_h_far, -far),
260 Vec3::new(half_w_far, half_h_far, -far),
261 Vec3::new(-half_w_far, half_h_far, -far),
262 ];
263
264 let mut lines = Vec::with_capacity(16);
265
266 for i in 0..4 {
267 let j = (i + 1) % 4;
268 lines.push(GizmoLineInstance::new(
269 transform.transform_point(near_corners[i]),
270 transform.transform_point(near_corners[j]),
271 color,
272 ));
273 }
274
275 for i in 0..4 {
276 let j = (i + 1) % 4;
277 lines.push(GizmoLineInstance::new(
278 transform.transform_point(far_corners[i]),
279 transform.transform_point(far_corners[j]),
280 color,
281 ));
282 }
283
284 for i in 0..4 {
285 lines.push(GizmoLineInstance::new(
286 transform.transform_point(near_corners[i]),
287 transform.transform_point(far_corners[i]),
288 color,
289 ));
290 }
291
292 lines
293}
294
295pub fn wireframe_sphere(
297 transform: &Mat4,
298 radius: f32,
299 segments: u32,
300 color: [f32; 4],
301) -> Vec<GizmoLineInstance> {
302 let mut lines = Vec::new();
303
304 for ring in 0..3 {
305 let phi = std::f32::consts::PI * ((ring as f32 + 0.5) / 3.0 - 0.5);
306 let ring_radius = radius * phi.cos();
307 let y = radius * phi.sin();
308 for seg in 0..segments {
309 let theta0 = 2.0 * std::f32::consts::PI * (seg as f32 / segments as f32);
310 let theta1 = 2.0 * std::f32::consts::PI * ((seg + 1) as f32 / segments as f32);
311 let p0 = Vec3::new(ring_radius * theta0.cos(), y, ring_radius * theta0.sin());
312 let p1 = Vec3::new(ring_radius * theta1.cos(), y, ring_radius * theta1.sin());
313 lines.push(GizmoLineInstance::new(
314 transform.transform_point(p0),
315 transform.transform_point(p1),
316 color,
317 ));
318 }
319 }
320
321 for seg in 0..segments {
322 let theta = 2.0 * std::f32::consts::PI * (seg as f32 / segments as f32);
323 let dir = Vec3::new(theta.cos(), 0.0, theta.sin());
324 for step in 0..8 {
325 let phi0 = std::f32::consts::PI * (step as f32 / 8.0 - 0.5);
326 let phi1 = std::f32::consts::PI * ((step + 1) as f32 / 8.0 - 0.5);
327 let p0 = Vec3::new(
328 dir.x * radius * phi0.cos(),
329 radius * phi0.sin(),
330 dir.z * radius * phi0.cos(),
331 );
332 let p1 = Vec3::new(
333 dir.x * radius * phi1.cos(),
334 radius * phi1.sin(),
335 dir.z * radius * phi1.cos(),
336 );
337 lines.push(GizmoLineInstance::new(
338 transform.transform_point(p0),
339 transform.transform_point(p1),
340 color,
341 ));
342 }
343 }
344
345 lines
346}
347
348pub fn directional_light_icon(
350 transform: &Mat4,
351 length: f32,
352 color: [f32; 4],
353) -> Vec<GizmoLineInstance> {
354 let origin = transform.cols[3].truncate();
355 let dir = transform
356 .transform_vector(Vec3::new(0.0, 0.0, -1.0))
357 .normalize();
358 let tip = origin + dir * length;
359
360 let right = transform.transform_vector(Vec3::X).normalize();
361 let up = transform.transform_vector(Vec3::Y).normalize();
362 let head_size = length * 0.3;
363
364 vec![
365 GizmoLineInstance::new(origin, tip, color),
366 GizmoLineInstance::new(tip, tip - dir * head_size + right * head_size * 0.5, color),
367 GizmoLineInstance::new(tip, tip - dir * head_size - right * head_size * 0.5, color),
368 GizmoLineInstance::new(tip, tip - dir * head_size + up * head_size * 0.5, color),
369 GizmoLineInstance::new(tip, tip - dir * head_size - up * head_size * 0.5, color),
370 ]
371}
372
373#[derive(Debug, Clone)]
375pub struct SelectionGizmo {
376 pub transform: Mat4,
378 pub kind: GizmoKind,
380 pub size: f32,
382}
383
384pub fn generate_selection_gizmos(
386 selected_entities: &[SelectionGizmo],
387 gizmo_mode: GizmoMode,
388) -> Vec<GizmoLineInstance> {
389 let mut lines = Vec::new();
390
391 for SelectionGizmo {
392 transform,
393 kind,
394 size,
395 } in selected_entities
396 {
397 match kind {
398 GizmoKind::Empty => {
399 lines.extend(wireframe_cube(
400 transform,
401 Vec3::new(0.5, 0.5, 0.5),
402 [0.5, 0.5, 0.6, 1.0],
403 ));
404 }
405 GizmoKind::Camera {
406 fov_y,
407 aspect,
408 near,
409 far,
410 } => {
411 lines.extend(camera_frustum(
412 transform,
413 *fov_y,
414 *aspect,
415 *near,
416 *far,
417 [0.35, 0.63, 0.97, 1.0],
418 ));
419 }
420 GizmoKind::DirectionalLight => {
421 lines.extend(directional_light_icon(
422 transform,
423 1.0,
424 [1.0, 0.95, 0.5, 1.0],
425 ));
426 }
427 GizmoKind::PointLight { radius } => {
428 lines.extend(wireframe_sphere(
429 transform,
430 *radius,
431 12,
432 [1.0, 0.7, 0.3, 1.0],
433 ));
434 }
435 GizmoKind::Audio => {
436 lines.extend(wireframe_sphere(transform, 0.4, 8, [0.7, 0.3, 1.0, 1.0]));
437 }
438 GizmoKind::Mesh => {
439 lines.extend(wireframe_cube(
440 transform,
441 Vec3::new(0.5, 0.5, 0.5),
442 [0.3, 0.85, 0.6, 1.0],
443 ));
444 }
445 }
446
447 if gizmo_mode == GizmoMode::Select {
453 lines.extend(transform_axes(transform, *size));
454 }
455 }
456
457 lines
458}
459
460#[derive(Debug, Clone)]
462pub enum GizmoKind {
463 Empty,
465 Camera {
467 fov_y: f32,
469 aspect: f32,
471 near: f32,
473 far: f32,
475 },
476 DirectionalLight,
478 PointLight {
480 radius: f32,
482 },
483 Audio,
485 Mesh,
487}