use crate::ecs::world::World;
use crate::mcp::McpResponse;
fn spawn_mesh_by_type(world: &mut World, mesh_type: &str) -> Option<freecs::Entity> {
match mesh_type.to_lowercase().as_str() {
"cube" => Some(crate::ecs::world::spawn_cube_at(
world,
nalgebra_glm::Vec3::zeros(),
)),
"sphere" => Some(crate::ecs::world::spawn_sphere_at(
world,
nalgebra_glm::Vec3::zeros(),
)),
"cylinder" => Some(crate::ecs::world::spawn_cylinder_at(
world,
nalgebra_glm::Vec3::zeros(),
)),
"cone" => Some(crate::ecs::world::spawn_cone_at(
world,
nalgebra_glm::Vec3::zeros(),
)),
"plane" => Some(crate::ecs::world::spawn_plane_at(
world,
nalgebra_glm::Vec3::zeros(),
)),
"torus" => Some(crate::ecs::world::spawn_torus_at(
world,
nalgebra_glm::Vec3::zeros(),
)),
_ => None,
}
}
pub(crate) fn mcp_spawn_entity(
world: &mut World,
request: crate::mcp::SpawnEntityRequest,
) -> McpResponse {
let crate::mcp::SpawnEntityRequest {
name,
mesh,
position,
scale,
color,
emissive,
parent: parent_name,
alpha: alpha_mode,
} = request;
let scale = scale.unwrap_or([1.0, 1.0, 1.0]);
use crate::ecs::transform::components::Parent;
use crate::ecs::world::{GLOBAL_TRANSFORM, LOCAL_TRANSFORM, NAME, PARENT};
if world.resources.entity_names.contains_key(&name) {
return McpResponse::Error(format!("Entity '{}' already exists", name));
}
let parent_entity = if let Some(ref pname) = parent_name {
let Some(&parent) = world.resources.entity_names.get(pname) else {
return McpResponse::Error(format!("Parent entity '{}' not found", pname));
};
Some(parent)
} else {
None
};
let pivot = world.spawn_entities(LOCAL_TRANSFORM | GLOBAL_TRANSFORM | PARENT, 1)[0];
if let Some(transform) = world.core.get_local_transform_mut(pivot) {
transform.translation = nalgebra_glm::Vec3::new(position[0], position[1], position[2]);
}
crate::ecs::transform::commands::mark_local_transform_dirty(world, pivot);
if let Some(parent) = parent_entity {
world.update_parent(pivot, Some(Parent(Some(parent))));
}
let Some(entity) = spawn_mesh_by_type(world, &mesh) else {
crate::ecs::world::commands::despawn_recursive_immediate(world, pivot);
return McpResponse::Error(format!(
"Unknown mesh type '{}'. Valid types: Cube, Sphere, Cylinder, Cone, Plane, Torus",
mesh
));
};
world.update_parent(entity, Some(Parent(Some(pivot))));
if scale != [1.0, 1.0, 1.0] {
if let Some(transform) = world.core.get_local_transform_mut(pivot) {
transform.scale = nalgebra_glm::Vec3::new(scale[0], scale[1], scale[2]);
}
crate::ecs::transform::commands::mark_local_transform_dirty(world, pivot);
}
let material_name = name.clone();
let mut material = crate::ecs::material::components::Material::default();
if let Some(c) = color {
material.base_color = c;
}
if let Some(e) = emissive {
material.emissive_factor = e;
}
if let Some(ref alpha) = alpha_mode {
material.alpha_mode = match alpha.to_lowercase().as_str() {
"opaque" => crate::ecs::material::components::AlphaMode::Opaque,
"mask" => crate::ecs::material::components::AlphaMode::Mask,
"blend" => crate::ecs::material::components::AlphaMode::Blend,
_ => crate::ecs::material::components::AlphaMode::Opaque,
};
}
crate::ecs::material::resources::material_registry_insert(
&mut world.resources.material_registry,
material_name.clone(),
material,
);
if let Some(&index) = world
.resources
.material_registry
.registry
.name_to_index
.get(&material_name)
{
world
.resources
.material_registry
.registry
.add_reference(index);
}
world.core.set_material_ref(
entity,
crate::ecs::material::components::MaterialRef::new(material_name),
);
world.core.add_components(pivot, NAME);
world
.core
.set_name(pivot, crate::ecs::name::components::Name(name.clone()));
world.resources.entity_names.insert(name.clone(), pivot);
let parent_info = parent_name
.map(|p| format!(" (child of '{}')", p))
.unwrap_or_default();
McpResponse::Success(format!(
"Spawned entity '{}' with {} mesh at {:?}{}",
name, mesh, position, parent_info
))
}
pub(crate) fn mcp_despawn_entity(world: &mut World, name: String) -> McpResponse {
if let Some(entity) = world.resources.entity_names.remove(&name) {
crate::ecs::world::commands::despawn_recursive_immediate(world, entity);
McpResponse::Success(format!("Despawned entity '{}'", name))
} else {
McpResponse::Error(format!("Entity '{}' not found", name))
}
}
pub(crate) fn mcp_query_entity(
world: &World,
request: crate::mcp::QueryEntityRequest,
) -> Result<McpResponse, McpResponse> {
let entity = super::resolve_entity(world, &request.name)?;
let Some(transform) = world.core.get_local_transform(entity) else {
return Err(McpResponse::Error(format!(
"Entity '{}' has no transform",
request.name
)));
};
let tags = world
.resources
.entity_tags
.get(&entity)
.cloned()
.unwrap_or_default();
let metadata = world
.resources
.entity_metadata
.get(&entity)
.map(|map| {
map.iter()
.map(|(key, value)| (key.clone(), metadata_value_to_json(value)))
.collect()
})
.unwrap_or_default();
Ok(McpResponse::EntityInfo(Box::new(
crate::mcp::EntityInfoData {
name: request.name,
position: [
transform.translation.x,
transform.translation.y,
transform.translation.z,
],
rotation: [
transform.rotation.i,
transform.rotation.j,
transform.rotation.k,
transform.rotation.w,
],
scale: [transform.scale.x, transform.scale.y, transform.scale.z],
tags,
metadata,
},
)))
}
fn metadata_value_to_json(value: &crate::ecs::scene::MetadataValue) -> serde_json::Value {
match value {
crate::ecs::scene::MetadataValue::Bool(v) => serde_json::Value::Bool(*v),
crate::ecs::scene::MetadataValue::Integer(v) => serde_json::json!(*v),
crate::ecs::scene::MetadataValue::Float(v) => serde_json::json!(*v),
crate::ecs::scene::MetadataValue::String(v) => serde_json::Value::String(v.clone()),
crate::ecs::scene::MetadataValue::Array(arr) => {
serde_json::Value::Array(arr.iter().map(metadata_value_to_json).collect())
}
crate::ecs::scene::MetadataValue::Map(map) => {
let obj: serde_json::Map<String, serde_json::Value> = map
.iter()
.map(|(k, v)| (k.clone(), metadata_value_to_json(v)))
.collect();
serde_json::Value::Object(obj)
}
}
}
fn json_to_metadata_value(value: &serde_json::Value) -> crate::ecs::scene::MetadataValue {
match value {
serde_json::Value::Bool(v) => crate::ecs::scene::MetadataValue::Bool(*v),
serde_json::Value::Number(n) => {
if let Some(integer) = n.as_i64() {
crate::ecs::scene::MetadataValue::Integer(integer)
} else {
crate::ecs::scene::MetadataValue::Float(n.as_f64().unwrap_or(0.0))
}
}
serde_json::Value::String(s) => crate::ecs::scene::MetadataValue::String(s.clone()),
serde_json::Value::Array(arr) => crate::ecs::scene::MetadataValue::Array(
arr.iter().map(json_to_metadata_value).collect(),
),
serde_json::Value::Object(map) => {
let converted: std::collections::HashMap<String, crate::ecs::scene::MetadataValue> =
map.iter()
.map(|(k, v)| (k.clone(), json_to_metadata_value(v)))
.collect();
crate::ecs::scene::MetadataValue::Map(converted)
}
serde_json::Value::Null => crate::ecs::scene::MetadataValue::String(String::new()),
}
}
pub(crate) fn mcp_set_entity_tags(
world: &mut World,
request: crate::mcp::SetEntityTagsRequest,
) -> Result<McpResponse, McpResponse> {
let entity = super::resolve_entity(world, &request.name)?;
if request.tags.is_empty() {
world.resources.entity_tags.remove(&entity);
} else {
world
.resources
.entity_tags
.insert(entity, request.tags.clone());
}
Ok(McpResponse::Success(format!(
"Set {} tags on '{}'",
request.tags.len(),
request.name
)))
}
pub(crate) fn mcp_set_entity_metadata(
world: &mut World,
request: crate::mcp::SetEntityMetadataRequest,
) -> Result<McpResponse, McpResponse> {
let entity = super::resolve_entity(world, &request.name)?;
if request.metadata.is_empty() {
world.resources.entity_metadata.remove(&entity);
} else {
let converted: std::collections::HashMap<String, crate::ecs::scene::MetadataValue> =
request
.metadata
.iter()
.map(|(key, value)| (key.clone(), json_to_metadata_value(value)))
.collect();
world.resources.entity_metadata.insert(entity, converted);
}
Ok(McpResponse::Success(format!(
"Set {} metadata entries on '{}'",
request.metadata.len(),
request.name
)))
}
pub(crate) fn mcp_rename_entity(
world: &mut World,
request: crate::mcp::RenameEntityRequest,
) -> Result<McpResponse, McpResponse> {
if world.resources.entity_names.contains_key(&request.new_name) {
return Err(McpResponse::Error(format!(
"Entity '{}' already exists",
request.new_name
)));
}
let entity = world
.resources
.entity_names
.remove(&request.name)
.ok_or_else(|| McpResponse::Error(format!("Entity '{}' not found", request.name)))?;
world
.resources
.entity_names
.insert(request.new_name.clone(), entity);
world.core.set_name(
entity,
crate::ecs::name::components::Name(request.new_name.clone()),
);
Ok(McpResponse::Success(format!(
"Renamed '{}' to '{}'",
request.name, request.new_name
)))
}
pub(crate) fn mcp_clear_scene(world: &mut World) -> McpResponse {
let entities_to_despawn: Vec<_> = world.resources.entity_names.values().copied().collect();
let count = entities_to_despawn.len();
for entity in entities_to_despawn {
crate::ecs::world::commands::despawn_recursive_immediate(world, entity);
}
world.resources.entity_names.clear();
McpResponse::Success(format!("Cleared {} entities from scene", count))
}
pub(crate) fn mcp_set_visibility(
world: &mut World,
request: crate::mcp::SetVisibilityRequest,
) -> Result<McpResponse, McpResponse> {
use crate::ecs::visibility::components::Visibility;
let entity = super::resolve_entity(world, &request.name)?;
world.core.set_visibility(
entity,
Visibility {
visible: request.visible,
},
);
Ok(McpResponse::Success(format!(
"Visibility set to {} for '{}'",
request.visible, request.name
)))
}