Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Engine/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Engine/apps/agent-backend/src/editor_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ pub fn tools() -> Vec<Value> {
(
"object_update",
"Atomically update scene object properties through editor undo. Use editor_status for IDs and values. Rotation is quaternion xyzw; vectors are xyz. Components use the returned JSON structure.",
json!({"type": "object", "required": ["entity_id", "properties"], "properties": {"entity_id": {"type": "string"}, "properties": {"type": "object", "additionalProperties": false, "properties": {"name": {"type": "string"}, "parent": {"type": ["string", "null"]}, "part_attributes": {"type": "object"}, "camera": {"type": ["object", "null"]}, "light": {"type": ["object", "null"]}, "scripts": {"type": "array", "items": {"type": "object"}}, "translation": {"type": "array", "items": {"type": "number"}, "minItems": 3, "maxItems": 3}, "rotation": {"type": "array", "items": {"type": "number"}, "minItems": 4, "maxItems": 4}, "scale": {"type": "array", "items": {"type": "number"}, "minItems": 3, "maxItems": 3}}}}}),
json!({"type": "object", "required": ["entity_id", "properties"], "properties": {"entity_id": {"type": "string"}, "properties": {"type": "object", "additionalProperties": false, "properties": {"name": {"type": "string"}, "parent": {"type": ["string", "null"]}, "part_attributes": {"type": "object"}, "camera": {"type": ["object", "null"]}, "light": {"type": ["object", "null"]}, "effect": {"type": ["object", "null"], "description":"Serialized EffectComponent: common plus kind with exactly one named variant, e.g. {kind:{Particle:{rate:30}}}; null removes. Read existing structure from editor_status."}, "scripts": {"type": "array", "items": {"type": "object"}}, "translation": {"type": "array", "items": {"type": "number"}, "minItems": 3, "maxItems": 3}, "rotation": {"type": "array", "items": {"type": "number"}, "minItems": 4, "maxItems": 4}, "scale": {"type": "array", "items": {"type": "number"}, "minItems": 3, "maxItems": 3}}}}}),
),
(
"object_create",
"Create an authored folder, empty object, camera or light, optionally inside parent (null means scene root). Use folders and nested subfolders to organize growing scenes by purpose/area; parent is the immediate live UUID, not a path. Folders do not create separate scenes or transform children. For finished meshes use model_create; primitives are for deliberate blockouts/colliders.",
json!({"type":"object","required":["name"],"properties":{"name":{"type":"string"},"kind":{"type":"string","enum":["empty","folder","camera","light","blockout"]},"parent":{"type":["string","null"],"description":"Existing destination object/folder UUID, or null for scene root; defaults to root."}}}),
json!({"type":"object","required":["name"],"properties":{"name":{"type":"string"},"kind":{"type":"string","enum":["empty","folder","camera","light","blockout"]},"parent":{"type":["string","null"],"description":"Existing destination object/folder UUID, or null for scene root; defaults to root."},"effect":{"type":["object","null"],"description":"Optional typed EffectComponent: {kind:{Particle:{rate:30}}}; missing fields use validated defaults."}}}),
),
(
"object_duplicate",
Expand Down
10 changes: 8 additions & 2 deletions Engine/apps/browser/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,15 @@ pub extern "C" fn rustic_tick(width: u32, height: u32, seconds: f64) {
game.session
.step(seconds, input.keys, input.pointers, input.reset)?;
let [width, height] = engine_play::game_view_extent([width, height]);
let scene = game.session.render_world(|world| {
renderer_wgpu::game_scene_with_models(world, 16.0 / 9.0, &game.models)
let (scene, effects) = game.session.render_world(|world| {
(
renderer_wgpu::game_scene_with_models(world, 16.0 / 9.0, &game.models),
world.effect_frame().clone(),
)
})?;
game.renderer
.set_effect_frame(&effects)
.map_err(|error| error.to_string())?;
#[cfg(target_os = "emscripten")]
{
let audio: Vec<u8> = game
Expand Down
73 changes: 72 additions & 1 deletion Engine/apps/editor/src/agent_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ impl EditorApp {
let objects = self.document.entity_snapshots().map_err(|e| e.to_string())?.iter().map(|s| json!({
"entity_id":s.id,"name":s.name,"folder":s.folder,"parent":s.parent,"translation":s.local_transform.translation.to_array(),
"rotation":s.local_transform.rotation.to_array(),"scale":s.local_transform.scale.to_array(),
"part_attributes":s.part_attributes,"camera":s.camera,"light":s.light,"scripts":s.scripts,
"part_attributes":s.part_attributes,"camera":s.camera,"light":s.light,"effect":s.effect,"scripts":s.scripts,
"mesh_asset":s.mesh.map(|m| m.asset),"material_asset":s.material.map(|m| m.asset),"primitive":s.primitive,
"textures":s.textures,"resolved_textures":self.document.world().resolved_textures(s.id)
})).collect::<Vec<_>>();
Expand Down Expand Up @@ -234,6 +234,17 @@ impl EditorApp {
before: before.light,
after: decode(value)?,
}),
"effect" => {
let after: Option<engine_world::EffectComponent> = decode(value)?;
if let Some(effect) = &after {
effect.validate()?;
}
edits.push(engine_world::SceneEdit::Effect {
entity: id,
before: before.effect.clone(),
after,
});
}
"scripts" => edits.push(engine_world::SceneEdit::Scripts {
entity: id,
before: before.scripts.clone(),
Expand Down Expand Up @@ -278,6 +289,12 @@ impl EditorApp {
}
_ => return Err("kind must be empty, folder, camera, light or blockout".into()),
}
if let Some(value) = args.get("effect") {
object.effect = decode(value)?;
if let Some(effect) = &object.effect {
effect.validate()?;
}
}
let id = object.id;
self.document
.edit_transaction(
Expand Down Expand Up @@ -805,6 +822,60 @@ mod tests {
mod authoring_flow_tests {
use super::*;
use engine_project::Project;
#[test]
fn effect_authoring_tools_validate_and_undo_component_edits() {
let temp = tempfile::tempdir().unwrap();
let project = Project::create(
temp.path().join("effects"),
"Effects",
engine_project::ProjectTemplate::Blank,
)
.unwrap();
let context = egui::Context::default();
let mut app = EditorApp::new(project, &context).unwrap();
let object = app
.agent_tool(
"object_create",
&json!({"name":"Sparks","effect":{"kind":{"Particle":{"rate":60}}}}),
&context,
)
.unwrap();
let id: EntityId = decode(&object["entity_id"]).unwrap();
let original = app.document.world().effect(id).unwrap();
assert!(original.is_some());
assert!(
app.agent_tool(
"object_update",
&json!({"entity_id":id,"properties":{"effect":{"common":{"duration":0}}}}),
&context
)
.is_err()
);
assert_eq!(app.document.world().effect(id).unwrap(), original);
app.agent_tool(
"object_update",
&json!({"entity_id":id,"properties":{"effect":null}}),
&context,
)
.unwrap();
assert!(app.document.world().effect(id).unwrap().is_none());
app.agent_tool("editor_undo", &json!({}), &context).unwrap();
assert_eq!(app.document.world().effect(id).unwrap(), original);
app.agent_tool("scene_save", &json!({}), &context).unwrap();
let loaded =
engine_world::load_scene(&std::fs::read(app.document.scene_path()).unwrap()).unwrap();
assert_eq!(
loaded
.document
.entities
.iter()
.find(|e| e.id == id)
.unwrap()
.effect,
original
);
}

#[test]
#[allow(
clippy::too_many_lines,
Expand Down
Loading
Loading