16#include <hpp/utility.hpp>
24auto entity_to_json(entt::handle
entity,
int depth,
int max_depth) -> std::string
29auto parse_light_type(
const std::string& value,
light_type& out) ->
bool
31 if(value ==
"directional" || value ==
"Directional")
36 if(value ==
"point" || value ==
"Point")
41 if(value ==
"spot" || value ==
"Spot")
56 auto old_parent =
entity.get<transform_component>().get_parent();
57 em.do_action<transform_set_parent_action_t>(
"MCP Set Parent",
entity, old_parent, parent);
68 auto old_pos =
transform.get_position_global();
70 em.do_action<transform_move_global_action_t>(
"MCP Set Position",
entity, old_pos,
position);
75 const simdjson::dom::object& args,
86 em.do_action<transform_move_action_t>(
"MCP Set Position Local",
93 em.do_action<transform_move_global_action_t>(
"MCP Set Position World",
105 const auto old_euler =
transform.get_rotation_euler_local();
108 "MCP Set Rotation Local",
111 if(
auto* t =
entity.try_get<transform_component>())
113 t->set_rotation_euler_local(new_euler);
118 if(
auto* t =
entity.try_get<transform_component>())
120 t->set_rotation_euler_local(old_euler);
126 const auto old_euler =
transform.get_rotation_euler_global();
129 "MCP Set Rotation World",
132 if(
auto* t =
entity.try_get<transform_component>())
134 t->set_rotation_euler_global(new_euler);
139 if(
auto* t =
entity.try_get<transform_component>())
141 t->set_rotation_euler_global(old_euler);
152 em.do_action<transform_scale_action_t>(
"MCP Set Scale Local",
159 const auto old_scale =
transform.get_scale_global();
160 const auto new_scale =
scale;
162 "MCP Set Scale World",
165 if(
auto* t =
entity.try_get<transform_component>())
167 t->set_scale_global(new_scale);
172 if(
auto* t =
entity.try_get<transform_component>())
174 t->set_scale_global(old_scale);
181auto normalize_scene_key(std::string key) -> std::string
188 const fs::path as_path(key);
189 if(as_path.is_absolute() && fs::exists(as_path, ec))
193 if(
key.size() < 5 ||
key.substr(
key.size() - 5) !=
".spfb")
205 {.name=
"scene_get_info",
206 .description=
"Get active scene info: tag/source, entity count, and play mode phase.",
208 .handler=[](
rtti::context& ctx,
const simdjson::dom::object&) -> tool_result
211 auto* scn = em.get_active_scene(ctx);
212 std::string
phase =
"inactive";
213 if(ctx.
has<play_mode>())
220 else if(play.is_simulation_running())
224 else if(play.is_active())
230 if(!scn || !scn->registry)
232 return {R
"({"has_scene":false,"play_phase":")" + phase + "\"}",
false};
235 const auto entity_count = scn->registry->storage<entt::entity>().
size();
236 const auto source = scn->source ? scn->source.id() : std::string{};
237 return {.text=fmt::format(R
"({{"has_scene":true,"tag":{},"source":{},"entity_count":{},"play_phase":{}}})",
244 .mutates_scene=
false});
247 {.name=
"scene_list_entities_batch",
249 "List entities in the active scene hierarchy. Optional parent_id and max_depth (default 2). "
250 "Axes: X-right, Y-up, Z-forward. "
251 "Transform fields: position/rotation_euler/scale are WORLD (global); "
252 "position_local/rotation_euler_local/scale_local are LOCAL (parent-relative).",
253 .input_schema_json=R
"({"type":"object","properties":{"parent_id":{"type":"string"},"max_depth":{"type":"integer","minimum":0}}})",
254 .handler=[](rtti::context& ctx, const simdjson::dom::object& args) -> tool_result
257 auto* scn = em.get_active_scene(ctx);
258 if(!scn || !scn->registry)
260 return {.text=
"No active scene", .is_error=
true};
263 int64_t max_depth = 2;
264 if(args[
"max_depth"].get(max_depth))
276 std::string json =
"[";
285 json += entity_to_json(
entity, 0,
static_cast<int>(max_depth));
293 return {.text=
"Entity not found: " +
parent_id, .is_error=
true};
295 if(
auto* transform = parent.try_get<transform_component>())
297 for(
auto child :
transform->get_children())
305 scn->registry->view<root_component, transform_component>().each(
306 [&](
auto e,
auto&&,
auto&& transform)
313 return {.text=json, .is_error=
false};
315 .mutates_scene=
false});
318 {.name=
"scene_create_light",
320 "Create a light entity. Args: light_type (directional|point|spot), name, optional parent_id/position. "
321 "position is WORLD space.",
322 .input_schema_json=R
"json({"type":"object","properties":{"light_type":{"type":"string"},"name":{"type":"string"},"parent_id":{"type":"string"},"position":{"type":"array","items":{"type":"number"},"minItems":3,"maxItems":3,"description":"WORLD space [x,y,z] (X-right, Y-up, Z-forward)"}},"required":["light_type","name"]})json",
323 .handler=[](rtti::context& ctx, const simdjson::dom::object& args) -> tool_result
325 scene* scn =
nullptr;
329 return {.text=
error, .is_error=
true};
332 std::string type_name;
336 return {.text=
"Missing light_type or name", .is_error=
true};
339 if(!parse_light_type(type_name,
type))
341 return {.text=
"Invalid light_type", .is_error=
true};
344 entt::handle parent{};
351 return {.text=
"Parent not found: " +
parent_id, .is_error=
true};
358 entt::handle created{};
360 em.do_action<create_entities_action_t>(
"MCP Create Light",
368 return {.text=
"Failed to create light", .is_error=
true};
370 maybe_set_parent(ctx, created, parent);
373 maybe_set_position(ctx, created,
position);
375 return {.text=entity_to_json(created, 0, 0), .is_error=
false};
377 .mutates_scene=
true});
380 {.name=
"scene_create_camera",
382 "Create a camera entity. Args: name, optional parent_id/position. position is WORLD space.",
383 .input_schema_json=R
"json({"type":"object","properties":{"name":{"type":"string"},"parent_id":{"type":"string"},"position":{"type":"array","items":{"type":"number"},"minItems":3,"maxItems":3,"description":"WORLD space [x,y,z] (X-right, Y-up, Z-forward)"}},"required":["name"]})json",
384 .handler=[](rtti::context& ctx, const simdjson::dom::object& args) -> tool_result
386 scene* scn =
nullptr;
390 return {.text=
error, .is_error=
true};
396 return {.text=
"Missing name", .is_error=
true};
399 entt::handle parent{};
406 return {.text=
"Parent not found: " +
parent_id, .is_error=
true};
413 entt::handle created{};
415 em.do_action<create_entities_action_t>(
"MCP Create Camera",
423 return {.text=
"Failed to create camera", .is_error=
true};
425 maybe_set_parent(ctx, created, parent);
428 maybe_set_position(ctx, created,
position);
430 return {.text=entity_to_json(created, 0, 0), .is_error=
false};
432 .mutates_scene=
true});
435 {.name=
"scene_delete_entities_batch",
436 .description=
"Delete one or more entities by id.",
437 .input_schema_json=R
"({"type":"object","properties":{"entity_ids":{"type":"array","items":{"type":"string"}}},"required":["entity_ids"]})",
438 .handler=[](rtti::context& ctx, const simdjson::dom::object& args) -> tool_result
440 scene* scn =
nullptr;
444 return {.text=
error, .is_error=
true};
447 simdjson::dom::array ids;
448 if(args[
"entity_ids"].get(ids))
450 return {.text=
"Missing entity_ids", .is_error=
true};
453 std::vector<entt::handle> entities;
456 std::string_view id_view;
459 return {.text=
"entity_ids must be strings", .is_error=
true};
464 return {.text=
"Entity not found: " + std::string(id_view), .is_error=
true};
466 entities.push_back(
entity);
469 ctx.
get_cached<editing_manager>().do_action<delete_entities_action_t>(
"MCP Delete Entities", entities);
470 return {.text=fmt::format(R
"({{"deleted":{}}})", entities.size()), .is_error=false};
472 .mutates_scene=
true});
475 {.name=
"scene_list_component_types",
476 .description=
"List addable component pretty names for scene_add_components_batch.",
478 .handler=[](
rtti::context&,
const simdjson::dom::object&) -> tool_result
480 std::string json =
"[";
482 hpp::for_each_tuple_type<all_addable_components>(
486 auto type = entt::resolve<ctype>();
495 return {.text=json, .is_error=
false};
497 .mutates_scene=
false});
500 {.name =
"scene_list_presets",
502 "List defaults::scene_preset values usable with scene_new_from_preset / project_open "
503 "(low, medium, high, showcase).",
506 [](
rtti::context&,
const simdjson::dom::object&) -> tool_result
508 return {.text = R
"(["low","medium","high","showcase"])", .is_error = false};
510 .mutates_scene =
false});
513 {.name =
"scene_save",
515 "Save the active edit scene to a .spfb via asset_writer::atomic_save_to_file. "
516 "Omit key/path to overwrite scene.source; provide key or absolute path for save-as "
517 "(sets scene.source). Requires an open project. Refuses play mode and prefab mode.",
519 R
"json({"type":"object","properties":{"key":{"type":"string","description":"Asset key e.g. app:/data/Village.spfb"},"path":{"type":"string","description":"Absolute filesystem path to a .spfb"}}})json",
521 [](rtti::context& ctx, const simdjson::dom::object& args) -> tool_result
526 return {.text =
error, .is_error =
true};
530 if(em.is_prefab_mode())
532 return {.text =
"Cannot scene_save while in prefab mode", .is_error =
true};
536 auto&
scene = ec.get_scene();
542 if(
key.empty() && !path.empty())
551 return {.text =
"Scene has no source; provide key or path for save-as", .is_error =
true};
557 key = normalize_scene_key(key);
563 return {.text =
"Failed to save scene: " +
key, .is_error =
true};
566 return {.text = fmt::format(R
"({{"ok":true,"key":{},"path":{}}})",
571 .mutates_scene =
true});
574 {.name =
"scene_open",
576 "Open a scene asset (.spfb) by key or absolute path. No ImGui save prompt; pass "
577 "force:true (default) to discard unsaved changes. Requires an open project.",
579 R
"json({"type":"object","properties":{"key":{"type":"string","description":"Asset key e.g. app:/data/MyScene.spfb"},"path":{"type":"string","description":"Absolute filesystem path to a .spfb"},"force":{"type":"boolean","default":true}},"required":[]})json",
581 [](rtti::context& ctx, const simdjson::dom::object& args) -> tool_result
586 return {.text =
error, .is_error =
true};
593 if(
key.empty() && !path.empty())
599 return {.text =
"Provide key or path", .is_error =
true};
601 key = normalize_scene_key(key);
607 if(em.has_unsaved_changes() && !
force)
609 return {.text =
"Unsaved scene changes; pass force:true to discard", .is_error =
true};
613 em.clear_unsaved_changes();
617 auto asset = am.get_asset<scene_prefab>(
key);
620 return {.text =
"Scene asset not found: " +
key, .is_error =
true};
625 return {.text =
error, .is_error =
true};
628 auto* scn = em.get_active_scene(ctx);
629 size_t entity_count = 0;
630 if(scn && scn->registry)
632 for(
auto entity : scn->registry->view<transform_component>())
638 return {.text = fmt::format(R
"({{"ok":true,"key":{},"entity_count":{}}})",
643 .mutates_scene =
true});
646 {.name =
"scene_new_from_preset",
648 "Create a new unsaved scene from defaults::scene_preset (camera, skylight, probe, "
649 "volume). No ImGui modal. Presets: low|medium|high|showcase (default medium). "
650 "Requires an open project. force:true (default) discards unsaved changes.",
652 R
"({"type":"object","properties":{"preset":{"type":"string","enum":["low","medium","high","showcase"]},"force":{"type":"boolean","default":true}}})",
654 [](rtti::context& ctx, const simdjson::dom::object& args) -> tool_result
659 return {.text =
error, .is_error =
true};
662 std::string preset_str =
"medium";
667 return {.text =
"Invalid preset (use low|medium|high|showcase)", .is_error =
true};
674 if(em.has_unsaved_changes() && !
force)
676 return {.text =
"Unsaved scene changes; pass force:true to discard", .is_error =
true};
680 em.clear_unsaved_changes();
685 auto* scn = em.get_active_scene(ctx);
686 size_t entity_count = 0;
687 if(scn && scn->registry)
689 for(
auto entity : scn->registry->view<transform_component>())
695 return {.text = fmt::format(R
"({{"ok":true,"preset":{},"entity_count":{}}})",
700 .mutates_scene =
true});
defaults::scene_preset preset
auto get_pretty_name(const meta_type &t) -> std::string
path resolve_protocol(const path &_path)
Given the specified path/filename, resolve the final full filename. This will be based on either the ...
path convert_to_protocol(const path &_path)
Oposite of the resolve_protocol this function tries to convert to protocol path from an absolute one.
bgfx::Transform transform
auto require_not_play_mode(rtti::context &ctx, std::string &error) -> bool
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 make_json_string(const std::string &value) -> std::string
void register_scene_tools(mcp_tool_registry ®istry)
auto read_string(const simdjson::dom::object &args, const char *key, std::string &out) -> bool
auto require_open_project(rtti::context &ctx, std::string &error) -> bool
std::tuple< test_component, model_component, animation_component, camera_component, volume_component, auto_exposure_component, tonemapping_component, assao_component, bloom_component, fxaa_component, taa_component, ssr_component, ssil_component, light_component, skylight_component, reflection_probe_component, physics_component, character_controller_component, audio_source_component, audio_listener_component, text_component, particle_emitter_component, ui_document_component > all_addable_components
light_type
Enum representing the type of light.
auto entity_to_summary_json(entt::handle entity, int depth, int max_depth) -> std::string
JSON summary: transform, component pretty-names, optional children.
octet_iterator append(utfchar32_t cp, octet_iterator result)
The library API - functions intended to be called by the users.
std::vector< float > scale
std::vector< math::quat > rotation
static auto parse_scene_preset(hpp::string_view value, scene_preset &out) -> bool
Parse a preset name (low|medium|high|showcase; aliases: standard/default -> medium)....
scene_preset
Quality presets for new scene creation (low = less expensive, high = more expensive).
static auto scene_preset_to_string(scene_preset preset) -> const char *
Stable string for logging / MCP / UI ("low", "medium", "high", "showcase").
static auto create_light_entity(rtti::context &ctx, scene &scn, light_type type, const std::string &name) -> entt::handle
Creates a light entity.
static auto create_camera_entity(rtti::context &ctx, scene &scn, const std::string &name) -> entt::handle
Creates a camera entity.
static auto load_scene_from_asset(rtti::context &ctx, const asset_handle< scene_prefab > &asset, std::string *error=nullptr) -> bool
Non-modal scene load (shared by File menu + MCP). Clears edit state, loads asset, syncs prefabs,...
static auto save_scene_to_path(rtti::context &ctx, const fs::path &path, bool update_source=true, bool show_notification=true) -> bool
Atomic-save active scene to path/key. When update_source is true, sets scene.source and project opene...
static auto new_scene_from_preset(rtti::context &ctx, defaults::scene_preset preset) -> bool
Non-modal new scene from preset (shared by create-scene modal + MCP). Cancels any pending create-scen...