25 return ctx.get_cached<hub>().get_panels().get_scene_panel();
30 auto camera = resolve_scene_panel(ctx).get_camera();
31 if(!camera || !camera.all_of<transform_component, camera_component>())
33 error =
"Scene panel camera not available";
41 auto& hub_sys = ctx.get_cached<hub>();
42 auto camera = hub_sys.get_panels().get_scene_panel().get_camera();
48 auto* camera_comp = camera.try_get<camera_component>();
54 return camera_comp->get_render_view().fbo_safe_get(
"OBUFFER");
60 auto& em = ctx.get_cached<editing_manager>();
61 auto* scn = em.get_active_scene(ctx);
62 if(!scn || !scn->registry)
64 auto& ec = ctx.get_cached<ecs>();
65 scn = &ec.get_scene();
67 if(!scn || !scn->registry)
73 scn->registry->view<camera_component>().each(
74 [&](
auto,
auto&& camera_comp)
80 auto obuffer = camera_comp.get_render_view().fbo_safe_get(
"OBUFFER");
81 if(obuffer && obuffer->is_valid())
89auto vec3_to_json(
const math::vec3&
v) -> std::string
91 return fmt::format(
"[{:.6g},{:.6g},{:.6g}]",
v.x,
v.y,
v.z);
94auto camera_to_json(entt::handle camera) -> std::string
96 auto& tc = camera.get<transform_component>();
97 auto& cc = camera.get<camera_component>();
99 R
"({{"position":{},"rotation_euler":{},"forward":{},"up":{},"fov":{:.6g},"ortho_size":{:.6g}}})",
100 vec3_to_json(tc.get_position_global()),
101 vec3_to_json(tc.get_rotation_euler_global()),
102 vec3_to_json(tc.get_z_axis_global()),
103 vec3_to_json(tc.get_y_axis_global()),
105 cc.get_ortho_size());
108auto cancel_camera_focus() ->
void
113auto read_duration(
const simdjson::dom::object& args,
float default_duration = 0.4f) ->
float
115 double duration = default_duration;
116 (void)args[
"duration"].get(duration);
125 return static_cast<float>(duration);
129 const simdjson::dom::object& args,
130 std::vector<entt::handle>& out,
131 std::string&
error) ->
bool
133 scene* scn =
nullptr;
141 std::string single_id;
142 if(
read_string(args,
"entity_id", single_id) && !single_id.empty())
147 error =
"Entity not found: " + single_id;
153 simdjson::dom::array ids;
154 if(!args[
"entity_ids"].get(ids))
158 std::string_view id_view;
161 error =
"entity_ids must be an array of strings";
167 error =
"Entity not found: " + std::string(id_view);
176 error =
"Provide entity_id or entity_ids";
187 {.name =
"viewport_screenshot_scene",
188 .description =
"Capture a PNG screenshot of the Scene panel OBUFFER (editor viewport). "
189 "Blit + GPU readback of the offscreen color target (action + wait). "
190 "Returns an image content block plus metadata JSON.",
192 R
"({"type":"object","properties":{"wait_ms":{"type":"integer","minimum":100,"maximum":15000,"description":"Max time to wait for PNG after requesting capture (default 3000)."}}})",
199 .mutates_scene =
false,
200 .requires_main_thread =
false});
203 {.name =
"viewport_screenshot_game",
204 .description =
"Capture a PNG screenshot of the Game panel OBUFFER (active scene camera). "
205 "Blit + GPU readback of the offscreen color target (action + wait). "
206 "Returns an image content block plus metadata JSON.",
208 R
"({"type":"object","properties":{"wait_ms":{"type":"integer","minimum":100,"maximum":15000,"description":"Max time to wait for PNG after requesting capture (default 3000)."}}})",
215 .mutates_scene =
false,
216 .requires_main_thread =
false});
219 {.name =
"viewport_get_camera",
221 "Get the Scene panel editor camera pose (position, rotation_euler degrees, forward/up, "
222 "fov, ortho_size). This is the viewport camera, not a scene Camera entity.",
231 return {.text =
error, .is_error =
true};
233 return {.text = camera_to_json(
camera), .is_error =
false};
235 .mutates_scene =
false});
238 {.name =
"viewport_set_camera",
240 "Set Scene panel camera position and/or rotation_euler (degrees). "
241 "Axes: X-right, Y-up, Z-forward. Cancels any in-flight "
242 "focus animation. Optional relative:true applies position as a local-space offset.",
244 R
"json({"type":"object","properties":{"position":{"type":"array","items":{"type":"number"},"minItems":3,"maxItems":3},"rotation_euler":{"type":"array","items":{"type":"number"},"minItems":3,"maxItems":3},"relative":{"type":"boolean","description":"If true, position is added in camera local space (default false)"}}})json",
252 return {.text =
error, .is_error =
true};
259 if(!has_position && !has_rotation)
261 return {.text =
"Provide position and/or rotation_euler", .is_error =
true};
264 bool relative =
false;
267 cancel_camera_focus();
274 const auto world_delta = tc.get_x_axis_global() *
position.x +
275 tc.get_y_axis_global() *
position.y +
276 tc.get_z_axis_global() *
position.z;
277 tc.set_position_global(tc.get_position_global() + world_delta);
286 tc.set_rotation_euler_global(
rotation);
289 return {.text = camera_to_json(
camera), .is_error =
false};
291 .mutates_scene =
false});
294 {.name =
"viewport_look_at",
296 "Aim the Scene panel camera at a world-space target point. Optional position moves the "
297 "camera first; optional up vector (default world up).",
299 R
"json({"type":"object","properties":{"target":{"type":"array","items":{"type":"number"},"minItems":3,"maxItems":3},"position":{"type":"array","items":{"type":"number"},"minItems":3,"maxItems":3},"up":{"type":"array","items":{"type":"number"},"minItems":3,"maxItems":3}},"required":["target"]})json",
307 return {.text =
error, .is_error =
true};
313 return {.text =
"Missing target [x,y,z]", .is_error =
true};
316 cancel_camera_focus();
328 tc.look_at(target, up);
335 return {.text = fmt::format(R
"({{"ok":true,"target":{},"camera":{}}})",
336 vec3_to_json(target),
340 .mutates_scene =
false});
343 {.name =
"viewport_focus_entities_batch",
345 "Focus the Scene panel camera on one or more scene entities using "
346 "defaults::focus_camera_on_entities (same as hierarchy F / double-click). "
347 "Keeps current rotation unless aim:true (look at bounds center first). "
348 "duration default 0.4s; use 0 for instant.",
350 R
"json({"type":"object","properties":{"entity_id":{"type":"string"},"entity_ids":{"type":"array","items":{"type":"string"}},"duration":{"type":"number","minimum":0,"maximum":10},"aim":{"type":"boolean","description":"Look at the entities' bounds center before focusing (default false)"}}})json",
358 return {.text =
error, .is_error =
true};
361 std::vector<entt::handle> entities;
362 if(!resolve_focus_entities(ctx, args, entities,
error))
364 return {.text =
error, .is_error =
true};
369 const float duration = read_duration(args, 0.4f);
371 cancel_camera_focus();
376 return {.text = fmt::format(
377 R
"({{"ok":true,"count":{},"duration":{:.3g},"aim":{},"camera":{}}})",
380 aim ? "true" :
"false",
384 .mutates_scene =
false});
387 {.name =
"viewport_focus_bounds",
389 "Focus the Scene panel camera on a world-space sphere (center+radius) or box "
390 "(min+max) via defaults::focus_camera_on_bounds. Optional aim:true looks at center first.",
392 R
"json({"type":"object","properties":{"center":{"type":"array","items":{"type":"number"},"minItems":3,"maxItems":3},"radius":{"type":"number","minimum":0},"min":{"type":"array","items":{"type":"number"},"minItems":3,"maxItems":3},"max":{"type":"array","items":{"type":"number"},"minItems":3,"maxItems":3},"duration":{"type":"number","minimum":0,"maximum":10},"aim":{"type":"boolean"}}})json",
400 return {.text =
error, .is_error =
true};
403 const float duration = read_duration(args, 0.4f);
411 const bool has_min =
read_vec3(args,
"min", min_v);
412 const bool has_max =
read_vec3(args,
"max", max_v);
415 const bool has_radius = !args[
"radius"].get(radius);
417 cancel_camera_focus();
419 if(has_min && has_max)
430 else if(has_center && has_radius)
445 return {.text =
"Provide center+radius, or min+max", .is_error =
true};
448 return {.text = fmt::format(R
"({{"ok":true,"duration":{:.3g},"aim":{},"camera":{}}})",
450 aim ? "true" :
"false",
454 .mutates_scene =
false});
457 {.name =
"viewport_orbit_camera",
459 "Orbit the Scene panel camera around a world pivot by yaw/pitch degrees (Y-up then "
460 "camera-right). Defaults pivot to current look target estimate from focus, or "
461 "explicit pivot. Keeps distance to pivot.",
463 R
"json({"type":"object","properties":{"yaw":{"type":"number","description":"Degrees around world up"},"pitch":{"type":"number","description":"Degrees around camera right"},"pivot":{"type":"array","items":{"type":"number"},"minItems":3,"maxItems":3},"distance":{"type":"number","minimum":0.01,"description":"Optional override distance to pivot"}}})json",
471 return {.text =
error, .is_error =
true};
476 (void)args[
"yaw"].get(yaw);
477 (void)args[
"pitch"].get(pitch);
478 if(yaw == 0.0 && pitch == 0.0)
480 return {.text =
"Provide yaw and/or pitch in degrees", .is_error =
true};
483 cancel_camera_focus();
487 math::vec3 pivot =
position + tc.get_z_axis_global() * 5.0f;
490 double distance = 0.0;
491 if(args[
"distance"].get(distance) || distance <= 0.0)
493 distance =
static_cast<double>(math::length(
position - pivot));
502 tc.rotate_around_global(pivot, math::vec3{0.0f, 1.0f, 0.0f},
static_cast<float>(yaw));
506 tc.rotate_around_global(pivot, tc.get_x_axis_global(),
static_cast<float>(pitch));
510 auto offset = tc.get_position_global() - pivot;
511 if(math::length(offset) > 1e-6f)
513 offset = math::normalize(offset) *
static_cast<float>(distance);
514 tc.set_position_global(pivot + offset);
518 return {.text = fmt::format(R
"({{"ok":true,"pivot":{},"yaw":{:.3g},"pitch":{:.3g},"camera":{}}})",
525 .mutates_scene =
false});
528 {.name =
"viewport_reset_camera",
530 "Reset the Scene panel camera to the default editor pose (same as Scene panel "
531 "Reset Camera button).",
536 cancel_camera_focus();
537 auto& panel = resolve_scene_panel(ctx);
538 panel.reset_camera(ctx);
544 return {.text =
error, .is_error =
true};
546 return {.text = fmt::format(R
"({{"ok":true,"camera":{}}})", camera_to_json(camera)),
549 .mutates_scene =
false});
std::shared_ptr< frame_buffer > ptr
Provides storage for common representation of spherical bounding volume, and wraps up common function...
Class representing a camera. Contains functionality for manipulating and updating a camera....
void stop_all(const std::string &scope)
Stops all actions within the specified scope.
auto read_vec3(const simdjson::dom::object &args, const char *key, math::vec3 &out) -> bool
auto empty_object_schema() -> std::string
auto find_entity(scene &scn, const std::string &id) -> entt::handle
auto require_edit_scene(rtti::context &ctx, scene *&out_scene, std::string &error) -> bool
auto read_bool(const simdjson::dom::object &args, const char *key, bool &out) -> bool
auto read_wait_ms(const simdjson::dom::object &args, int64_t default_ms=1000) -> std::chrono::milliseconds
auto capture_fbo_screenshot(mcp_manager &mcp, rtti::context &ctx, const std::function< gfx::frame_buffer::ptr(rtti::context &)> &resolve_fbo, const std::string &tag, std::chrono::milliseconds wait_timeout) -> tool_result
auto read_string(const simdjson::dom::object &args, const char *key, std::string &out) -> bool
void register_viewport_tools(mcp_tool_registry ®istry)
@ sphere
Sphere type reflection probe.
@ box
Box type reflection probe.
std::vector< math::quat > rotation
Storage for box vector values and wraps up common functionality.
bbox & add_point(const vec3 &point)
Grows the bounding box based on the point passed.
static void focus_camera_on_entities(entt::handle camera, hpp::span< const entt::handle > entities, float duration=0.0f)
Focuses a camera on a specified entity with a timed transition.
static void focus_camera_on_bounds(entt::handle camera, const math::bsphere &bounds, float duration=0.0f)
Focuses a camera on bounds with a timed transition.