khora_editor/
mod_gizmo.rs1use khora_sdk::editor_ui::gizmo::manipulator;
23use khora_sdk::editor_ui::{
24 generate_selection_gizmos, gizmo_basis, gizmo_world_size, EditorState, GizmoBasis, GizmoDelta,
25 GizmoKind, GizmoLineInstance, GizmoTransform, SelectionGizmo,
26};
27use khora_sdk::khora_core::math::{Mat4, Vec3};
28use khora_sdk::khora_core::renderer::api::resource::ViewInfo;
29use khora_sdk::khora_core::renderer::api::scene::mesh::Mesh;
30use khora_sdk::khora_core::renderer::light::LightType;
31use khora_sdk::prelude::ecs::{AudioSource, Camera, EntityId, GlobalTransform, Light, Transform};
32use khora_sdk::GameWorld;
33use khora_sdk::HandleComponent;
34
35pub struct SelectionFrame {
37 pub pivot: Vec3,
39 pub size: f32,
41 pub basis: GizmoBasis,
43}
44
45pub fn selection_frame(
51 world: &GameWorld,
52 editor_state: &EditorState,
53 view_info: &ViewInfo,
54) -> Option<SelectionFrame> {
55 let mut sum = Vec3::ZERO;
56 let mut count = 0.0_f32;
57 let mut rotation = None;
58
59 for &entity in &editor_state.selection {
60 let Some(transform) = world.get_component::<Transform>(entity) else {
61 continue;
62 };
63 sum = sum + world_origin(world, entity);
64 count += 1.0;
65 rotation.get_or_insert(transform.rotation);
68 }
69
70 (count > 0.0).then(|| {
71 let pivot = sum / count;
72 SelectionFrame {
73 pivot,
74 size: gizmo_world_size(view_info, pivot),
75 basis: gizmo_basis(editor_state.gizmo_mode, rotation.unwrap_or_default()),
76 }
77 })
78}
79
80pub fn collect_gizmo_lines(
82 world: &GameWorld,
83 editor_state: &EditorState,
84 view_info: &ViewInfo,
85) -> Vec<GizmoLineInstance> {
86 let frame = selection_frame(world, editor_state, view_info);
87 let mut entries: Vec<SelectionGizmo> = Vec::new();
88
89 for &entity_id in &editor_state.selection {
90 let Some(transform) = world.get_component::<Transform>(entity_id) else {
91 continue;
92 };
93 let world_matrix = world
96 .get_component::<GlobalTransform>(entity_id)
97 .map(|global| global.0 .0)
98 .unwrap_or_else(|| {
99 Mat4::from_translation(transform.translation)
100 * Mat4::from_quat(transform.rotation)
101 * Mat4::from_scale(transform.scale)
102 });
103
104 let kind = if world.get_component::<Camera>(entity_id).is_some() {
105 GizmoKind::Camera {
106 fov_y: std::f32::consts::FRAC_PI_4,
107 aspect: 16.0 / 9.0,
108 near: 0.1,
109 far: 1000.0,
110 }
111 } else if let Some(light) = world.get_component::<Light>(entity_id) {
112 match &light.light_type {
113 LightType::Directional(_) => GizmoKind::DirectionalLight,
114 LightType::Point(p) => GizmoKind::PointLight { radius: p.range },
115 LightType::Spot(s) => GizmoKind::PointLight { radius: s.range },
116 }
117 } else if world.get_component::<AudioSource>(entity_id).is_some() {
118 GizmoKind::Audio
119 } else if world
120 .get_component::<HandleComponent<Mesh>>(entity_id)
121 .is_some()
122 {
123 GizmoKind::Mesh
124 } else {
125 GizmoKind::Empty
126 };
127
128 entries.push(SelectionGizmo {
129 transform: world_matrix,
130 kind,
131 size: gizmo_world_size(view_info, world_matrix.cols[3].truncate()),
132 });
133 }
134
135 let mut lines = generate_selection_gizmos(&entries, editor_state.gizmo_mode);
136
137 if let Some(frame) = frame {
140 lines.extend(manipulator(
141 frame.pivot,
142 &frame.basis,
143 editor_state.gizmo_mode,
144 frame.size,
145 ));
146 }
147
148 lines
149}
150
151pub fn capture_starts(
158 world: &GameWorld,
159 editor_state: &EditorState,
160) -> Vec<(EntityId, GizmoTransform)> {
161 editor_state
162 .selection
163 .iter()
164 .filter_map(|&entity| {
165 let transform = world.get_component::<Transform>(entity)?;
166 Some((
167 entity,
168 GizmoTransform {
169 translation: transform.translation,
170 rotation: transform.rotation,
171 scale: transform.scale,
172 },
173 ))
174 })
175 .collect()
176}
177
178pub fn apply_delta(
185 world: &mut GameWorld,
186 delta: GizmoDelta,
187 starts: &[(EntityId, GizmoTransform)],
188) {
189 for &(entity, start) in starts {
190 let result = delta.apply(start);
191 let Some(transform) = world.get_component_mut::<Transform>(entity) else {
192 continue;
193 };
194 transform.translation = result.translation;
195 transform.rotation = result.rotation;
196 transform.scale = result.scale;
197 }
198}
199
200fn world_origin(world: &GameWorld, entity: EntityId) -> Vec3 {
202 if let Some(global) = world.get_component::<GlobalTransform>(entity) {
203 return global.0 .0.cols[3].truncate();
204 }
205 world
206 .get_component::<Transform>(entity)
207 .map(|t| t.translation)
208 .unwrap_or(Vec3::ZERO)
209}