diff --git a/docs/SCENE_EFFECTS.md b/docs/SCENE_EFFECTS.md index b71b2de..e964cae 100644 --- a/docs/SCENE_EFFECTS.md +++ b/docs/SCENE_EFFECTS.md @@ -2,14 +2,14 @@ Scene effects are authored entity components. They use the existing hierarchy, transforms, scene persistence and undo history. An entity has at most one effect component; use child entities to combine effects. Configuration is saved; live particles and playback clocks are transient. -This guide describes the native editor and native Play path. CPU simulation is the supported particle path. The compatibility section identifies renderer features that remain unavailable. +This guide describes the native editor and Play path, with capability-gated GPU particles and CPU fallback. The compatibility section describes renderer and browser limits. ## Add and preview an effect 1. Open a project and scene in the editor. 2. Use hierarchy **+ Add → Effects** to create an effect entity, or select an entity and open **Properties → Scene effects → + Add / replace effect**. 3. Select the new effect entity. Expand **Common** for playback, seed, duration and visibility, then the effect-specific section for simulation and appearance. -4. Open **Properties → Effect preview** to play, pause, resume, stop, restart, reset or step by 1/60 second. Preview speed ranges from 0 to 4; time scrubbing replays up to 30 seconds, and bounds/metrics are available. Move its entity with the normal transform gizmo. An emitting trail needs movement before it creates a ribbon. Preview runs a scratch SceneWorld through the same simulation path, including registered child-asset sub-emitters. Stopping preview clears transient child groups. +4. Open **Properties → Effect preview** to play, pause, resume, stop, restart, reset or step by 1/60 second. Preview speed ranges from 0 to 4; time scrubbing replays up to 30 seconds, and bounds/metrics are available. Use the Inspector gizmo selector for **Entity transform**, **Emission-volume size**, **Beam start**, **Beam end** or **Force-wind direction**. These use the existing transform-gizmo handles; releasing records one undo transaction and Escape restores the original. Enable preview audio explicitly to hear composite cues; scrubbing stays silent. An emitting trail needs movement before it creates a ribbon. Preview runs a scratch SceneWorld through the same simulation path, including registered child-asset sub-emitters. Stopping preview clears transient child groups. 5. Save the scene, close it and reopen it. The configuration returns; playback starts from the configured initial state. Open **Effects presets & assets**, search the built-in library and click a preset to instantiate a group under the selection. Drag a preset onto the viewport or hierarchy to place it. Use **Load .effect** or **Save selection .effect** for custom assets. The preset is editable. For a selected single-effect component, **Assign single-effect asset…** changes its source. Composite assets are instantiated through the asset browser. Configure entity references such as a light pulse's target or a beam's endpoint; effects requiring targets cannot infer your intended object. @@ -74,9 +74,9 @@ A teleport farther than `teleport_distance` clears the previous strip so it does ## Effect asset and composite tutorial -An `.effect` file contains `EffectAsset { version, name, effects }`, with optional `entity_ids` and column-major `transforms` arrays for authored child identities and transforms relative to the group. Instantiation allocates fresh child IDs and remaps references between children, preserving their relative transforms. Legacy assets without those arrays instantiate at the group origin. References to scene objects outside the asset remain external references; assign them for the destination scene. Version 1 stores an ordered list of fully typed child configurations. `common.delay` and `common.duration` give each child its position in the composite timeline. The asset reader rejects unsupported versions, empty groups, groups above 128 children and files above 4 MiB. +An `.effect` file contains `EffectAsset { version, name, effects }`, with optional `entity_ids` and column-major `transforms` arrays for authored child identities and transforms relative to the group. Aligned light, primitive, mesh, material, texture and part-attribute arrays preserve ordinary child components. Optional `group_settings`, `sound_cues`, `parameters`, `bindings` and `trigger_bindings` describe the composite timeline. Instantiation allocates fresh child IDs and remaps references between children, preserving their relative transforms. Legacy assets without those arrays instantiate at the group origin. References to scene objects outside the asset remain external references; assign them for the destination scene. Version 1 stores an ordered list of fully typed child configurations. `common.delay` and `common.duration` give each child its position in the composite timeline. The asset reader rejects unsupported versions, empty groups, groups above 128 children and files above 4 MiB. -`presets()` returns 35 independent built-in assets; `instantiate()` clones a group's configuration. `instantiate_from(asset_id)` additionally retains the source UUID and child index; `restore(instance)` resets that child to its shared asset defaults. Editing one instance does not mutate another instance or the source asset. Explosion combines a time-zero burst, delayed smoke, an expanding shockwave and a camera impulse. The showcase generator writes all built-in assets under `assets/effects`. +`presets()` returns 35 independent built-in assets; `instantiate()` clones a group's configuration. `instantiate_from(asset_id)` additionally retains the source UUID and child index; `restore(instance)` resets that child to its shared asset defaults. Editing one instance does not mutate another instance or the source asset. Explosion combines a time-zero burst, delayed smoke, an expanding shockwave, mesh debris, a light flash, camera impulse and a generated sound cue. The preset installer provides the stable-ID WAV and debris OBJ with normal asset sidecars. Object dissolve and Character highlight include self-targeting demonstration geometry. The showcase generator writes all built-in assets under `assets/effects`. A composite's children are ordinary scene effects. Parenting preserves transform inheritance and standard duplication/deletion behavior. Reset individual instance values by reapplying the asset. Select the group and open **Scene effects → Composite timeline (seconds)** to change child enabled state, start and duration, select a child to edit, duplicate, move earlier or delete it. Select a source child for **Restore asset defaults** and **Update shared asset**. **Update shared asset** refreshes matching instances in the currently open authoring scene while preserving fields that differ from the old defaults; arrays are treated as complete overrides. That scene refresh is one undo transaction. The shared asset file write is a separate asset edit. When an asset is registered during scene loading, persisted source baselines merge current asset defaults while preserving per-field overrides; arrays remain atomic overrides. Legacy instances without a baseline retain embedded configuration until restored. @@ -148,17 +148,21 @@ Run this on an entity that does not already have an effect. For a previously aut JavaScript uses the same names and objects, with `null` instead of `nil`. Python also exposes the same `Effects` wrappers. Other generic-query adapters can use the existing `rustic.query` boundary, for example `{op:'effect_types'}`. Effect requests include `effect_create`, `effect_get`, `effect_configure`, `effect_control` and `effect_state`. Entity arguments in raw protocol requests must be stable UUIDs. Configuration uses the typed variant shape `kind: {Particle: {...}}`; unknown fields, unknown kinds and invalid values return errors before committing a scene mutation. -Runtime edits affect the live Play world. Stop Play to return to the authored scene. Use the Inspector and Save for persistent authoring. `dispose` removes the effect component, not its entity. Completion and particle birth/collision/death records are available in Rust `EffectFrame.events` and the existing Events service. Subscribe with `Events.connect(entity, "EffectCompleted", callback)` or `EffectBirth`, `EffectCollision` and `EffectDeath`; event arguments contain the world position vector. A composite root emits EffectCompleted once when its descendants complete; restarting or resetting begins a new completion cycle. `Effects.sampleWind(position, time, layers)` samples compatible wind at a position, using the supplied time in seconds for gust phase. +Runtime edits affect the live Play world. Stop Play to return to the authored scene. Use the Inspector and Save for persistent authoring. `dispose` removes the effect component, not its entity. Completion and particle birth/collision/death records are available in Rust `EffectFrame.events` and the existing Events service. Subscribe with `Events.connect(entity, "EffectCompleted", callback)` or `EffectBirth`, `EffectCollision` and `EffectDeath`; event arguments contain the world position vector. A composite root emits EffectCompleted once when its own timeline completes; its default duration includes child tails. Restarting or resetting begins a new completion cycle. `Effects.sampleWind(position, time, layers)` samples compatible wind at a position, using the supplied time in seconds for gust phase. ## Renderer compatibility and color management -The native renderer supports up to eight fog volumes, four shafts and four flares per frame. Fog raymarching uses 8–64 steps. Shafts use radial framebuffer sampling with depth occlusion and remain available with fog because the current fog model does not already produce equivalent directional shafts. Fog has height, edge and procedural noise density; anisotropic phase functions and dynamic-light scattering are unavailable. Depth of field uses a 25-tap depth-dependent blur focused at the center of the image. +The native renderer supports up to eight fog volumes, four shafts and four flares per frame. Fog raymarching uses 8–64 steps. Shafts use radial framebuffer sampling with depth occlusion and remain available with fog because the current fog model does not already produce equivalent directional shafts. Fog has height/edge density, procedural or textured noise, anisotropy and per-light scattering. Occlusion samples the available camera depth, so offscreen occluders are outside that approximation. Temporal fog/shaft history reprojects with motion/depth rejection and resets on camera cuts, resize and project changes. Motion blur uses object and camera motion vectors. Depth of field uses a 25-tap spatial blur with configurable focal distance and range. The native wgpu renderer batches depth-tested transparent geometry and particles. It supports Alpha, Additive and Multiply effects without a separate draw call for every billboard. Empty texture slots use procedural sprites. Geometry-based effects should use the existing mesh/material conventions when selecting assets. Mesh particles use batched source geometry through the PBR path with a 16-step color palette; a missing source mesh is omitted. Custom particle sprite atlases support up to 63 textures per frame, resampled to 256-pixel layers, with flipbook UVs and velocity stretching. Decal projection clips receiver triangles and supports albedo/normal asset textures plus scalar roughness/metallicity; packed ORM, decal surface-layer filtering and explicit material-asset overrides are unavailable. Outline color and thickness use depth-occluded stencil silhouettes; through-wall outlines are unavailable. -The existing material shader tone-maps before the resolved frame is post-processed. Effects grading decodes display colors for its calculations and re-encodes them. Bloom is a thresholded display-range filter; this is **not an HDR bloom pipeline**. Heat haze changes actual sampled framebuffer coordinates. GPU particle simulation is not implemented or advertised. CPU simulation is the supported path. +The native scene path renders linear HDR into `Rgba16Float`; bloom thresholds scene radiance before the final tone map. Heat haze changes sampled framebuffer coordinates. Particle `simulation_mode` supports `Cpu`, `Gpu` and `Auto`. The GPU path keeps particle state in compute buffers and draws it directly; unsupported authored modules fall back to CPU with an Inspector reason. Particle event generation, sorted transparency, collisions, textures/materials and force/noise modules require CPU simulation. The showcase includes a 10,000-particle Auto station. Browser WebGL uses the CPU path. -Unsupported advanced options must not be inferred from a configuration field alone. Current boundaries include GPU particle simulation, runtime cubemap capture, six-face cubemap file layouts, temporal volumetric history, and motion-vector motion blur. Baked local probes use a 2:1 equirectangular HDR/PNG/JPEG asset registered through its `.rmeta` ID; their bounds blend local reflection input and support box parallax correction. Overlap chooses the highest-priority probe per mesh rather than a weighted sum of probes. `cubemap` names that panorama asset. The showcase supplies a generated HDR panorama and sidecar. Water uses the reusable Rust `WaterSurface::sample(world_position)` interface, returning displacement, normal and foam. `RippleWaterSurface` combines a bounded queue of disturbances; `water_surface_mesh` builds a displaced grid. Configure target/source velocity, wavelength, damping, wake angle, foam and normal strength. This is an analytical cosmetic surface model, not a fluid solver. Browser export must be checked independently; the native effect renderer does not establish browser support. +Reflection probes capture six HDR faces in the native renderer on enable, at an interval or on explicit refresh. Captures include sky, scene meshes/lights and transparent particles; camera post effects and local fog post are excluded. Baked `.cubemap` assets describe six square HDR or LDR faces; legacy 2:1 panoramas remain supported. Probe quality controls prefilter sample count, overlap blends weighted probe input, and box parallax correction follows world transforms. Allocation is bounded to 16 maps and 1 GiB of retained probe textures. Water uses the reusable Rust `WaterSurface::sample(world_position)` interface, returning displacement, normal and foam. `RippleWaterSurface` combines a bounded queue of disturbances; `water_surface_mesh` builds a displaced grid. Configure target/source velocity, wavelength, damping, wake angle, foam and normal strength. This is an analytical cosmetic surface model, not a fluid solver. Browser export must be checked independently; the native effect renderer does not establish browser support. + +## Projection, light and camera envelopes + +Decals support box, sphere, cylinder and thin-plane projectors, receiver layer masks, ORM/emissive maps and a continuous distance fade before the configured render distance. Sphere/cylinder clipping uses a bounded convex approximation. `LightPulse.fade_in` and `fade_out` multiply its intensity curve using seconds within the cycle. Camera impulses expose `Additive`, `Replace` and signed-component `Maximum` blending in priority order. Their reset mode either returns to rest or holds the last active offset after natural completion until an explicit lifecycle command; authored camera transforms stay unchanged. ## Performance and cleanup diff --git a/docs/SCENE_EFFECTS_REFERENCE.md b/docs/SCENE_EFFECTS_REFERENCE.md index 66a5143..40eccc8 100644 --- a/docs/SCENE_EFFECTS_REFERENCE.md +++ b/docs/SCENE_EFFECTS_REFERENCE.md @@ -10,7 +10,7 @@ Configuration type: `EffectCommon`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | -| `parameters` | `std::collections::BTreeMap` | `Default::default()` | parameters | +| `parameters` | `std::collections::BTreeMap` | `std::collections::BTreeMap::default()` | parameters | | `bounds` | `Option` | `None` | bounds | | `enabled` | `bool` | `true` | enabled | | `visible` | `bool` | `true` | visible | @@ -34,6 +34,7 @@ Configuration type: `EffectAppearance`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | | `color` | `Gradient` | `Gradient::default()` | RGBA linear color | +| `color_parameter` | `Option` | `None` | color parameter | | `opacity` | `Curve` | `Curve::linear(1.0, 0.0)` | opacity | | `size` | `Curve` | `Curve::constant(1.0)` | size | | `material` | `Option` | `None` | material | @@ -61,6 +62,11 @@ Configuration type: `TrailRenderer`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | +| `start_width` | `Option` | `None` | start width | +| `end_width` | `Option` | `None` | end width | +| `corner_vertices` | `u32` | `0` | corner vertices | +| `cap_vertices` | `u32` | `0` | cap vertices | +| `smoothing` | `u32` | `0` | smoothing | | `emitting` | `bool` | `true` | emitting | | `lifetime` | `f32` | `1.0` | seconds or [minimum, maximum] seconds | | `width` | `f32` | `0.2` | scene units | @@ -76,6 +82,23 @@ Configuration type: `ParticleEmitter`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | +| `soft_particle_distance` | `f32` | `0.0` | soft particle distance | +| `cast_shadows` | `bool` | `true` | cast shadows | +| `receive_shadows` | `bool` | `true` | receive shadows | +| `sorting` | `ParticleSortMode` | `ParticleSortMode::Distance` | sorting | +| `turbulence` | `f32` | `0.0` | turbulence | +| `simulation_mode` | `super::ParticleSimulationMode` | `super::ParticleSimulationMode::Cpu` | simulation mode | +| `initial_velocity` | `[f32; 3]` | `[0.0; 3]` | initial velocity | +| `start_scale` | `[[f32; 3]; 2]` | `[[1.0; 3]; 2]` | start scale | +| `start_rotation` | `[[f32; 3]; 2]` | `[[0.0; 3], [0.0, 0.0, std::f32::consts::TAU]]` | start rotation | +| `angular_velocity_range` | `[[f32; 3]; 2]` | `[[0.0; 3]; 2]` | angular velocity range | +| `velocity_over_lifetime` | `VectorCurve` | `VectorCurve::default()` | velocity over lifetime | +| `rotation_over_lifetime` | `Curve` | `Curve::constant(0.0)` | rotation over lifetime | +| `mesh_variants` | `Vec` | `Vec::new()` | mesh variants | +| `sub_emitters` | `Vec` | `Vec::new()` | sub emitters | +| `offscreen_behavior` | `ParticleOffscreenBehavior` | `ParticleOffscreenBehavior::Simulate` | offscreen behavior | +| `collision_quality` | `ParticleCollisionQuality` | `ParticleCollisionQuality::Swept` | collision quality | +| `generate_events` | `bool` | `true` | generate events | | `prewarm` | `bool` | `false` | prewarm | | `prewarm_time` | `f32` | `5.0` | prewarm time | | `max_particles` | `u32` | `1000` | max particles | @@ -118,6 +141,11 @@ Configuration type: `BeamRenderer`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | +| `branch_probability` | `f32` | `1.0` | branch probability | +| `branch_width_scale` | `f32` | `0.5` | branch width scale | +| `pulse_speed` | `f32` | `0.0` | pulse speed | +| `endpoint_collision` | `bool` | `false` | endpoint collision | +| `collision_layers` | `u32` | `u32::MAX` | collision layers | | `start_target` | `Option` | `None` | start target | | `end_target` | `Option` | `None` | end target | | `start` | `[f32; 3]` | `[0.0; 3]` | start | @@ -136,6 +164,13 @@ Configuration type: `RingEffect`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | +| `expansion_speed` | `Option` | `None` | optional scene units / second | +| `edge_softness` | `f32` | `0.0` | edge softness | +| `align_to_surface` | `bool` | `false` | align to surface | +| `surface_layers` | `u32` | `u32::MAX` | surface layers | +| `displacement_strength` | `f32` | `0.0` | displacement strength | +| `displacement_frequency` | `f32` | `4.0` | displacement frequency | +| `screen_distortion` | `f32` | `0.0` | screen distortion | | `target` | `Option` | `None` | target | | `source_velocity` | `[f32; 3]` | `[0.0; 3]` | source velocity | | `wake_angle` | `f32` | `0.6` | wake angle | @@ -158,6 +193,23 @@ Configuration type: `ParticleEmitter`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | +| `soft_particle_distance` | `f32` | `0.0` | soft particle distance | +| `cast_shadows` | `bool` | `true` | cast shadows | +| `receive_shadows` | `bool` | `true` | receive shadows | +| `sorting` | `ParticleSortMode` | `ParticleSortMode::Distance` | sorting | +| `turbulence` | `f32` | `0.0` | turbulence | +| `simulation_mode` | `super::ParticleSimulationMode` | `super::ParticleSimulationMode::Cpu` | simulation mode | +| `initial_velocity` | `[f32; 3]` | `[0.0; 3]` | initial velocity | +| `start_scale` | `[[f32; 3]; 2]` | `[[1.0; 3]; 2]` | start scale | +| `start_rotation` | `[[f32; 3]; 2]` | `[[0.0; 3], [0.0, 0.0, std::f32::consts::TAU]]` | start rotation | +| `angular_velocity_range` | `[[f32; 3]; 2]` | `[[0.0; 3]; 2]` | angular velocity range | +| `velocity_over_lifetime` | `VectorCurve` | `VectorCurve::default()` | velocity over lifetime | +| `rotation_over_lifetime` | `Curve` | `Curve::constant(0.0)` | rotation over lifetime | +| `mesh_variants` | `Vec` | `Vec::new()` | mesh variants | +| `sub_emitters` | `Vec` | `Vec::new()` | sub emitters | +| `offscreen_behavior` | `ParticleOffscreenBehavior` | `ParticleOffscreenBehavior::Simulate` | offscreen behavior | +| `collision_quality` | `ParticleCollisionQuality` | `ParticleCollisionQuality::Swept` | collision quality | +| `generate_events` | `bool` | `true` | generate events | | `prewarm` | `bool` | `false` | prewarm | | `prewarm_time` | `f32` | `5.0` | prewarm time | | `max_particles` | `u32` | `1000` | max particles | @@ -200,6 +252,7 @@ Configuration type: `RibbonRenderer`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | +| `animation_speed` | `f32` | `0.0` | path units / second | | `points` | `Vec<[f32; 3]>` | `vec![[0.0; 3], [1.0, 1.0, 0.0], [2.0, 0.0, 0.0]]` | points | | `width` | `f32` | `0.2` | scene units | | `tube` | `bool` | `false` | tube | @@ -214,6 +267,14 @@ Configuration type: `ForceField`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | +| `vortex_speed` | `f32` | `1.0` | dimensionless angular multiplier | +| `drag_influence` | `f32` | `0.0` | drag influence | +| `gust_strength` | `f32` | `0.0` | acceleration / wind-strength units | +| `gust_frequency` | `f32` | `1.0` | Hz | +| `noise_scale` | `f32` | `1.0` | world-space noise divisor | +| `affect_particles` | `bool` | `true` | affect particles | +| `affect_foliage` | `bool` | `true` | affect foliage | +| `affect_materials` | `bool` | `true` | affect materials | | `shape` | `VolumeShape` | `VolumeShape::Sphere` | shape | | `dimensions` | `[f32; 3]` | `[5.0; 3]` | scene units, XYZ | | `force_type` | `ForceType` | `ForceType::Directional` | force type | @@ -231,6 +292,7 @@ Configuration type: `CollisionVolume`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | +| `quality` | `ParticleCollisionQuality` | `ParticleCollisionQuality::Swept` | quality | | `shape` | `VolumeShape` | `VolumeShape::Plane` | shape | | `dimensions` | `[f32; 3]` | `[10.0; 3]` | scene units, XYZ | | `layers` | `u32` | `u32::MAX` | unsigned bit mask | @@ -245,6 +307,11 @@ Configuration type: `ProjectileTracer`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | +| `start_position` | `[f32; 3]` | `[0.0; 3]` | start position | +| `end_position` | `Option<[f32; 3]>` | `None` | end position | +| `end_target` | `Option` | `None` | end target | +| `tail_fade` | `f32` | `1.0` | tail fade | +| `velocity_stretch` | `f32` | `0.0` | velocity stretch | | `velocity` | `[f32; 3]` | `[0.0, 0.0, 10.0]` | velocity | | `length` | `f32` | `2.0` | length | | `width` | `f32` | `0.05` | scene units | @@ -262,10 +329,14 @@ Configuration type: `FogVolume`. | `color` | `[f32; 3]` | `[0.6, 0.7, 0.8]` | RGBA linear color | | `height_falloff` | `f32` | `0.5` | height falloff | | `edge_falloff` | `f32` | `1.0` | edge falloff | -| `noise_scale` | `f32` | `1.0` | noise scale | +| `noise_scale` | `f32` | `1.0` | world-space noise divisor | | `noise_speed` | `f32` | `0.1` | noise speed | | `anisotropy` | `f32` | `0.0` | anisotropy | | `steps` | `u32` | `32` | steps | +| `noise_texture` | `Option` | `None` | noise texture | +| `light_scattering` | `f32` | `1.0` | light scattering | +| `max_distance` | `f32` | `1000.0` | scene units | +| `temporal_filtering` | `f32` | `0.85` | temporal filtering | ## 11. Decal @@ -273,6 +344,9 @@ Configuration type: `DecalProjector`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | +| `shape` | `VolumeShape` | `VolumeShape::Box` | shape | +| `emissive_map` | `Option` | `None` | emissive map | +| `distance_fade` | `f32` | `5.0` | scene units before common render distance | | `dimensions` | `[f32; 3]` | `[1.0, 1.0, 1.0]` | scene units, XYZ | | `appearance` | `EffectAppearance` | `EffectAppearance::default()` | appearance | | `normal_map` | `Option` | `None` | normal map | @@ -289,8 +363,13 @@ Configuration type: `WeatherVolume`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | +| `splash_effect` | `Option` | `None` | splash effect | +| `accumulation` | `f32` | `0.0` | accumulation | +| `accumulation_lifetime` | `f32` | `30.0` | accumulation lifetime | +| `max_accumulation` | `u32` | `1024` | max accumulation | +| `density_falloff` | `f32` | `0.0` | density falloff | | `weather` | `WeatherType` | `WeatherType::Rain` | weather | -| `emitter` | `ParticleEmitter` | `Box, [20,10,20], downward [8,10], rate 200, lifetime [2,3]` | emitter | +| `emitter` | `ParticleEmitter` | `emitter` | emitter | | `intensity` | `f32` | `1.0` | intensity | | `camera_follow` | `bool` | `false` | camera follow | @@ -300,6 +379,13 @@ Configuration type: `RingEffect`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | +| `expansion_speed` | `Option` | `None` | optional scene units / second | +| `edge_softness` | `f32` | `0.0` | edge softness | +| `align_to_surface` | `bool` | `false` | align to surface | +| `surface_layers` | `u32` | `u32::MAX` | surface layers | +| `displacement_strength` | `f32` | `0.0` | displacement strength | +| `displacement_frequency` | `f32` | `4.0` | displacement frequency | +| `screen_distortion` | `f32` | `0.0` | screen distortion | | `target` | `Option` | `None` | target | | `source_velocity` | `[f32; 3]` | `[0.0; 3]` | source velocity | | `wake_angle` | `f32` | `0.6` | wake angle | @@ -322,6 +408,14 @@ Configuration type: `ForceField`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | +| `vortex_speed` | `f32` | `1.0` | dimensionless angular multiplier | +| `drag_influence` | `f32` | `0.0` | drag influence | +| `gust_strength` | `f32` | `0.0` | acceleration / wind-strength units | +| `gust_frequency` | `f32` | `1.0` | Hz | +| `noise_scale` | `f32` | `1.0` | world-space noise divisor | +| `affect_particles` | `bool` | `true` | affect particles | +| `affect_foliage` | `bool` | `true` | affect foliage | +| `affect_materials` | `bool` | `true` | affect materials | | `shape` | `VolumeShape` | `VolumeShape::Sphere` | shape | | `dimensions` | `[f32; 3]` | `[5.0; 3]` | scene units, XYZ | | `force_type` | `ForceType` | `ForceType::Directional` | force type | @@ -340,10 +434,14 @@ Configuration type: `DistortionEffect`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | | `strength` | `f32` | `0.01` | strength | -| `noise_scale` | `f32` | `10.0` | noise scale | +| `noise_scale` | `f32` | `10.0` | world-space noise divisor | | `speed` | `f32` | `1.0` | scene units/second | | `radius` | `f32` | `3.0` | scene units | | `edge_falloff` | `f32` | `1.0` | edge falloff | +| `noise_texture` | `Option` | `None` | noise texture | +| `refraction_offset` | `[f32; 2]` | `[0.0; 2]` | refraction offset | +| `depth_masking` | `bool` | `true` | depth masking | +| `screen_space` | `bool` | `false` | screen space | ## 16. LightShaft @@ -356,8 +454,13 @@ Configuration type: `LightShaft`. | `color` | `[f32; 3]` | `[1.0, 0.9, 0.6]` | RGBA linear color | | `density` | `f32` | `0.1` | density | | `anisotropy` | `f32` | `0.5` | anisotropy | -| `max_distance` | `f32` | `100.0` | max distance | +| `max_distance` | `f32` | `100.0` | scene units | | `samples` | `u32` | `32` | samples | +| `occlusion` | `bool` | `true` | occlusion | +| `temporal_filtering` | `f32` | `0.85` | temporal filtering | +| `localized` | `bool` | `false` | localized | +| `shape` | `VolumeShape` | `VolumeShape::Box` | shape | +| `dimensions` | `[f32; 3]` | `[10.0; 3]` | dimensions | ## 17. FoliageScatter @@ -365,6 +468,16 @@ Configuration type: `FoliageScatter`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | +| `weighted_meshes` | `Vec` | `Vec::new()` | weighted meshes | +| `surface_target` | `Option` | `None` | surface target | +| `surface_triangles` | `Vec<[[f32; 3]; 3]>` | `Vec::new()` | surface triangles | +| `slope` | `[f32; 2]` | `[0.0, 90.0]` | slope | +| `height` | `[f32; 2]` | `[-1.0e8, 1.0e8]` | height | +| `rotation` | `[f32; 2]` | `[0.0, std::f32::consts::TAU]` | rotation | +| `align_to_surface` | `bool` | `true` | align to surface | +| `lod` | `Vec` | `Vec::new()` | lod | +| `cast_shadows` | `bool` | `true` | cast shadows | +| `receive_shadows` | `bool` | `true` | receive shadows | | `meshes` | `Vec` | `vec![]` | meshes | | `density` | `f32` | `1.0` | density | | `dimensions` | `[f32; 3]` | `[10.0, 1.0, 10.0]` | scene units, XYZ | @@ -380,11 +493,17 @@ Configuration type: `DissolveEffect`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | | `target` | `Option` | `None` | target | +| `target_material` | `Option` | `None` | target material | +| `noise_texture` | `Option` | `None` | noise texture | +| `direction_mask` | `Option` | `None` | direction mask | +| `direction` | `[f32; 3]` | `[0.0; 3]` | direction | +| `threshold` | `f32` | `0.0` | threshold | +| `particle_density` | `f32` | `0.0` | particle density | | `progress` | `f32` | `0.0` | progress | | `edge_width` | `f32` | `0.05` | edge width | | `edge_color` | `[f32; 3]` | `[1.0, 0.4, 0.0]` | edge color | | `edge_emission` | `f32` | `3.0` | edge emission | -| `noise_scale` | `f32` | `10.0` | noise scale | +| `noise_scale` | `f32` | `10.0` | world-space noise divisor | | `curve` | `Curve` | `Curve::linear(0.0, 1.0)` | curve | ## 19. AnimatedSurface @@ -394,12 +513,17 @@ Configuration type: `AnimatedSurface`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | | `target` | `Option` | `None` | target | +| `target_material` | `Option` | `None` | target material | | `uv_scroll` | `[f32; 2]` | `[0.2, 0.0]` | UV units/second | | `uv_scale` | `[f32; 2]` | `[1.0; 2]` | uv scale | | `uv_rotation` | `f32` | `0.0` | uv rotation | | `color` | `Gradient` | `Gradient::default()` | RGBA linear color | | `emission` | `f32` | `1.0` | emission | | `speed` | `f32` | `1.0` | scene units/second | +| `phase` | `f32` | `0.0` | phase | +| `distortion_map` | `Option` | `None` | distortion map | +| `distortion_strength` | `f32` | `0.0` | distortion strength | +| `mask_texture` | `Option` | `None` | mask texture | ## 20. LensFlare @@ -416,6 +540,9 @@ Configuration type: `LensFlare`. | `streak_length` | `f32` | `0.3` | streak length | | `fade_speed` | `f32` | `5.0` | fade speed | | `texture` | `Option` | `None` | texture | +| `streak_orientation` | `f32` | `0.0` | radians | +| `occlusion` | `bool` | `true` | occlusion | +| `max_distance` | `f32` | `1000.0` | scene units | ## 21. LightPulse @@ -423,6 +550,8 @@ Configuration type: `LightPulse`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | +| `fade_in` | `f32` | `0.0` | fade in | +| `fade_out` | `f32` | `0.0` | fade out | | `target` | `Option` | `None` | target | | `intensity` | `Curve` | `Curve::linear(0.0, 1.0)` | intensity | | `color` | `Gradient` | `Gradient::default()` | RGBA linear color | @@ -450,6 +579,16 @@ Configuration type: `PostProcessVolume`. | `chromatic_aberration` | `f32` | `0.0` | chromatic aberration | | `grain` | `f32` | `0.0` | grain | | `depth_of_field` | `f32` | `0.0` | depth of field | +| `motion_blur` | `f32` | `0.0` | motion blur | +| `motion_blur_samples` | `u32` | `12` | motion blur samples | +| `focus_distance` | `f32` | `0.0` | focus distance | +| `focus_range` | `f32` | `5.0` | focus range | +| `bloom_threshold` | `f32` | `1.0` | bloom threshold | +| `bloom_radius` | `f32` | `4.0` | bloom radius | +| `temporal_weight` | `f32` | `0.0` | temporal weight | +| `shape` | `VolumeShape` | `VolumeShape::Box` | shape | +| `camera` | `Option` | `None` | camera | +| `layers` | `u32` | `u32::MAX` | layers | ## 23. Outline @@ -469,6 +608,8 @@ Configuration type: `CameraShake`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | +| `blend_mode` | `CameraShakeBlendMode` | `CameraShakeBlendMode::default()` | blend mode | +| `reset_mode` | `CameraShakeResetMode` | `CameraShakeResetMode::default()` | reset mode | | `target` | `Option` | `None` | target | | `position_amplitude` | `[f32; 3]` | `[0.1; 3]` | scene units, XYZ | | `rotation_amplitude` | `[f32; 3]` | `[0.02; 3]` | radians, XYZ | @@ -489,6 +630,8 @@ Configuration type: `ScreenFlash`. | `fade_out` | `f32` | `0.4` | fade out | | `vignette` | `f32` | `0.5` | vignette | | `blend` | `BlendMode` | `BlendMode::Alpha` | blend | +| `screen_mask` | `Option` | `None` | screen mask | +| `stacking` | `OverlayStacking` | `OverlayStacking::Composite` | stacking | ## 26. ReflectionProbe @@ -496,13 +639,18 @@ Configuration type: `ReflectionProbe`. | Field | Type | Default | Units / interpretation | | --- | --- | --- | --- | +| `shape` | `VolumeShape` | `VolumeShape::Box` | shape | | `dimensions` | `[f32; 3]` | `[10.0; 3]` | scene units, XYZ | +| `capture_position` | `[f32; 3]` | `[0.0; 3]` | capture position | | `cubemap` | `Option` | `None` | cubemap | | `resolution` | `u32` | `128` | resolution | | `refresh` | `ProbeRefresh` | `ProbeRefresh::Baked` | refresh | | `blend_distance` | `f32` | `1.0` | blend distance | | `update_interval` | `f32` | `1.0` | update interval | | `parallax_correction` | `bool` | `false` | parallax correction | +| `visibility_layers` | `u32` | `u32::MAX` | visibility layers | +| `quality` | `QualityLevel` | `QualityLevel::Medium` | quality | +| `refresh_revision` | `u64` | `0` | refresh revision | ## Source asset identity @@ -515,3 +663,86 @@ Effect assets support version 1, one to 128 children, and at most 4 MiB. Duratio ## Composite child metadata EffectAsset version 1 optionally stores `entity_ids` and `transforms`, each empty or the same length as `effects`. IDs must be unique; transforms are column-major finite nonsingular TRS matrices relative to the group. `instances_with_ids(asset_id)` creates independent child snapshots, remaps internal targets to fresh IDs and records the remapped source baseline. Missing metadata preserves legacy identity transforms. + + +## ParticleSubEmitter + +Configuration type: `ParticleSubEmitter`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `trigger` | `ParticleTrigger` | `ParticleTrigger::Birth` | trigger | +| `effect` | `AssetId` | `AssetId::default()` | effect | +| `probability` | `f32` | `1.0` | probability | +| `inherit_velocity` | `f32` | `0.0` | inherit velocity | + + +## GroupParameterBinding + +Configuration type: `GroupParameterBinding`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `parameter` | `String` | `"power".into()` | parameter | +| `child` | `u32` | `0` | child | +| `property_path` | `String` | `"kind.Particle.rate".into()` | property path | +| `scale` | `f32` | `1.0` | scale | +| `offset` | `f32` | `0.0` | offset | + + +## GroupTriggerBinding + +Configuration type: `GroupTriggerBinding`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `source_child` | `u32` | `0` | source child | +| `event` | `CompositeTriggerEvent` | `Completed` | event | +| `target_child` | `u32` | `0` | target child | +| `action` | `CompositeTriggerAction` | `Play` | action | + + +## EffectGroupSettings + +Configuration type: `EffectGroupSettings`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `auto_play` | `bool` | `true` | auto play | +| `looping` | `bool` | `false` | looping | +| `duration` | `f32` | `5.0` | seconds | +| `time_scale` | `f32` | `1.0` | clock multiplier | + + +## EffectGroup + +Configuration type: `EffectGroup`. + +| Field | Type | Default | Units / interpretation | +| --- | --- | --- | --- | +| `source` | `Option` | `None` | source | +| `sound_cues` | `Vec` | `Vec::new()` | sound cues | +| `auto_play` | `bool` | `true` | auto play | +| `looping` | `bool` | `false` | looping | +| `duration` | `f32` | `5.0` | seconds | +| `time_scale` | `f32` | `1.0` | clock multiplier | +| `parameters` | `BTreeMap` | `BTreeMap::new()` | parameters | +| `bindings` | `Vec` | `Vec::new()` | bindings | +| `trigger_bindings` | `Vec` | `Vec::new()` | trigger bindings | +| `child_entities` | `Vec` | `Vec::new()` | child entities | +| `source_baseline` | `Option>` | `None` | source baseline | + +## Random color gradients and vector curves + +`appearance.color_parameter` is optional. `None` preserves the existing `appearance.color` gradient. `GradientParameter::Gradient(gradient)` evaluates one gradient; `BetweenGradients { min, max }` chooses a stable seeded interpolation factor for each particle and evaluates both gradients at the same normalized age. Replaying the same seed and time steps reproduces the choices. The advanced color parameter uses the CPU fallback. + +`VectorCurve` serializes transparently as three scalar curves in X, Y, Z order. For example, `velocity_over_lifetime` is `[curveX, curveY, curveZ]`, retaining the existing array representation. Every curve has finite time/value keys and defaults to zero. Curve evaluation clamps outside its endpoints and supports unsorted authored keys. + + +## Projection and impulse modes + +`DecalProjector.shape` accepts `Box`, `Sphere`, `Cylinder` or `Plane`. Sphere clipping uses a convex plane approximation with 24 azimuth and eight polar divisions; cylinder clipping uses 24 side planes. Plane uses a thin local Y slab. All shapes respect the projector transform and dimensions. `distance_fade` controls a continuous fade band before `common.render_distance`; `emissive_map` is an optional stable texture asset ID. ORM channels are red ambient occlusion, green roughness and blue metallic. + +`LightPulse.fade_in` and `fade_out` are explicit envelopes in seconds, multiplying the existing intensity curve; zero preserves the authored curve alone. Looping envelopes follow each cycle. + +`CameraShakeBlendMode` is `Additive` (default), `Replace` or `Maximum`. Impulses are applied in priority order. Maximum selects the largest absolute component while preserving its sign. `CameraShakeResetMode` is `ReturnToRest` (default) or `HoldFinal`; HoldFinal retains the last active offset after natural completion until an explicit stop/reset/restart/dispose. These render offsets never overwrite authored camera transforms. diff --git a/docs/Scripting/API/scene-effects.md b/docs/Scripting/API/scene-effects.md index df096aa..7851d90 100644 --- a/docs/Scripting/API/scene-effects.md +++ b/docs/Scripting/API/scene-effects.md @@ -4,7 +4,7 @@ Use `Effects` to control one typed effect component on an existing scene entity. ## Setup and attachment -Create a Lua or JavaScript **Object Component Script** through Programming, attach it to an entity in the Inspector, and press Play. Python has equivalent `Effects` wrappers in the engine-provided SDK; import `Effects` from `rustic`. Other SDKs use the existing generic `rustic.query` boundary with the request operations below. Do not install a third-party package named rustic. +Create a Lua or JavaScript **Object Component Script** through Programming, attach it to an entity in the Inspector, and press Play. Python has equivalent `Effects` wrappers in the engine-provided SDK; import `Effects` from `rustic`. Engine SDKs also provide Effects facades for C, C++, C#, Java and PHP over the same validated JSON query boundary. C query results remain owned by the engine session; copy data you need after the callback. Generic requests below are available in every SDK. Do not install a third-party package named rustic. ## Signatures @@ -14,6 +14,9 @@ Create a Lua or JavaScript **Object Component Script** through Programming, atta | `Effects.create(entity, kind)` | Creates an effect and returns its configuration; existing effect is an error | | `Effects.get(entity)` | Configuration, or null/nil when no component exists | | `Effects.configure(entity, settings)` | Validated recursive patch and resulting configuration | +| `Effects.group(entity)` | Composite timeline configuration, or null/nil | +| `Effects.configureGroup(entity, settings)` | Validated timeline/parameter/binding patch and resulting configuration | +| `Effects.refreshProbe(entity)` | Requests another six-face capture and returns its generation | | `Effects.play(entity)` | Begins playback | | `Effects.pause(entity)` | Freezes playback | | `Effects.resume(entity)` | Resumes playback | @@ -75,12 +78,32 @@ Use the generated Python starter's callback registration pattern if it differs f ## Events and generic requests -The existing Events service carries `EffectBirth`, `EffectCollision`, `EffectDeath` and `EffectCompleted`. Subscribe with `Events.connect(entity, name, callback)`; payload includes a world-position vector. Composite roots emit `EffectCompleted` once per completion cycle after child effects complete. Restart/reset starts a new cycle. Keep callbacks small and avoid recursively creating unbounded effects. Sub-emitter recursion is separately bounded by the runtime generation limit. +The existing Events service carries `EffectBirth`, `EffectCollision`, `EffectDeath` and `EffectCompleted`. Subscribe with `Events.connect(entity, name, callback)`; payload includes a world-position vector. Composite roots emit `EffectCompleted` once when their own timeline completes. Their default duration includes authored child delays, durations and particle/trail tails. Restart/reset starts a new cycle. Keep callbacks small and avoid recursively creating unbounded effects. Sub-emitter recursion is separately bounded by the runtime generation limit. -Generic operations are `effect_types`, `effect_create`, `effect_get`, `effect_configure`, `effect_control`, `effect_state` `effect_wind`, `effect_instantiate` and `effect_spawn`. For example, `rustic.query({op:'effect_create',entity:id,kind:'Particle'})` is equivalent to creation through the wrapper. Configuration requests use `settings`; controls use an `action` string such as `pause`. `effect_instantiate` accepts asset UUID, optional parent and position; `effect_spawn` requires position and a positive lifetime so a missing lifetime cannot silently create a persistent instance. Use the native query syntax for your language; do not construct private transport messages yourself. +Generic operations are `effect_types`, `effect_create`, `effect_get`, `effect_configure`, `effect_control`, `effect_state`, `effect_wind`, `effect_group_get`, `effect_group_configure`, `effect_refresh_probe`, `effect_instantiate` and `effect_spawn`. For example, `rustic.query({op:'effect_create',entity:id,kind:'Particle'})` is equivalent to creation through the wrapper. Configuration requests use `settings`; controls use an `action` string such as `pause`. `effect_instantiate` accepts asset UUID, optional parent and position; `effect_spawn` requires position and a positive lifetime so a missing lifetime cannot silently create a persistent instance. Use the native query syntax for your language; do not construct private transport messages yourself. ## Validation and lifetime Configuration has the shape `{source, common, kind}`. Kind settings are nested under the variant key, for example `{kind:{Particle:{rate:50}}}`. Unknown fields and invalid values return an error before the world transaction is committed. Use the [typed reference](/docs/guides/scene-effects/configuration) for exact fields, defaults and limits. -Effect configuration survives scene serialization when authored. Runtime clocks, live particles, script mutations and event queues do not become permanent scene edits. Entity destruction and scene unload release effect state. One component is allowed per entity; composite assets instantiate children as ordinary scene effects. Controls and state can target the composite group root; disposal removes descendant effect components and preserves the entity hierarchy. This API does not expose GPU particle simulation or automatically make cosmetic particle collisions authoritative gameplay physics. +Effect configuration survives scene serialization when authored. Runtime clocks, live particles, script mutations and event queues do not become permanent scene edits. Entity destruction and scene unload release effect state. One component is allowed per entity; composite assets instantiate children as ordinary scene effects. Controls and state can target the composite group root; disposal removes descendant effect components and preserves the entity hierarchy. Particle `simulation_mode` selects `Cpu`, `Gpu` or `Auto`. Unsupported features and browser WebGL use the CPU fallback; the Inspector reports the selected backend and reason. Cosmetic particle collisions remain separate from gameplay physics. + + +## Composite parameters and event tracks + +`Effects.group(root)` returns `auto_play`, `looping`, `duration`, `time_scale`, `sound_cues`, `parameters`, `bindings` and `trigger_bindings`. `source`, `source_baseline` and `child_entities` are read-only identity metadata. Group state comes from its own timeline; an early-finishing child does not complete the group. Pause and playback speed affect descendants and scheduled audio. Restart clears the previous cycle. + +```javascript +Effects.configureGroup(root, { + time_scale: 0.75, + parameters: {power: {Scalar: 1.5}}, + bindings: [{parameter: "power", child: 0, + property_path: "kind.Particle.rate", scale: 40, offset: 0}], + trigger_bindings: [{source_child: 0, event: "Completed", + target_child: 1, action: "Restart"}] +}); +``` + +A binding's zero-based `child` indexes the ordered `child_entities` list. Paths address existing `common` or `kind` properties; array entries use dot indices, such as `kind.LightPulse.intensity.keys.0.value`. Numeric bindings apply `value * scale + offset`. Vector and color dimensions must match; Boolean bindings require scale 1 and offset 0. Unknown paths, missing parameters, invalid child indices and invalid resulting values reject the entire transaction. Child configuration stays authored separately: binding values are applied to simulation copies. Parameter keys accept `null` to remove them, provided no surviving binding still requires them. + +Trigger events are `Birth`, `Collision`, `Death` and `Completed`. Actions are `Play`, `Pause`, `Resume`, `Stop`, `Restart` and `Reset`. Repeated events for the same target/action collapse into one command; at most 64 commands run per tick. Sub-emitter assets retain their composite timeline, audio, child lights and bindings. Recursion is bounded to eight generations, 128 spawned entities per tick and 1,024 temporary entities.