Composite
Composite components are made of multiple primitives or tattvas. They live under murali::frontend::collection::composite.
Beta Components​
Beta composites live under composite::beta and are not re-exported by Murali's main prelude.
Their APIs and choreography may change as they mature.
Opening​
Opening is an opinionated temporal composite for 3D capital-letter openings. It drops a title
into a perspective scene, shakes the settled letters apart, scatters glyph-shaped particles, and
reveals a tagline. The component creates normal tattvas and authors its sequence into either a
Timeline or a local-time Clip. See the dedicated Opening (Beta) guide for the full
style and timing reference, projection and background ownership, validation, standalone export,
and SceneView handoff.
use glam::Vec3;
use murali::frontend::collection::composite::beta::opening::{
Opening, OpeningStyle, OpeningTiming,
};
use murali::colors::{BLUE, GOLD, PINK, TEAL};
use murali::{BuiltinTexture, Clip, Scene, TextureImage, Timeline};
let opening = Opening::new("KAVRIQ", "The Science Behind AI")
.with_font_path("assets/fonts/brand-bold.ttf")
.with_texture(TextureImage::builtin(BuiltinTexture::WhiteMarble))
.with_style(OpeningStyle {
letter_gap: 0.34,
particle_count: 700,
particle_palette: vec![TEAL, BLUE, PINK, GOLD],
tagline_font_name: Some("Brand Sans".to_owned()),
..OpeningStyle::default()
})
.with_timing(OpeningTiming {
dissolve_duration: 0.82,
..OpeningTiming::default()
});
let ids = opening.add_to_scene(&mut scene, Vec3::ZERO)?;
let mut opening_clip = Clip::new();
ids.animate(&mut opening_clip);
let mut timeline = Timeline::new();
timeline.append(opening_clip);
scene.play(timeline)?;
Titles currently accept ASCII capitals and spaces. A custom 3D title font and texture are optional;
without a font path the bundled Inter font is used. The marble texture shown above is embedded in
Murali and needs no asset path; use with_texture_path for a project-owned image. The tagline uses
Murali's normal Label font registry: call register_font_path first, then set
tagline_font_name to the registered name.
The scene camera and export background remain host concerns, just as they are for other 3D
composites. For a standalone opening, export the scene directly and use the chosen background as
ExportSettings::clear_color. Wrap the scene in a SceneView only when it needs an independent
clock, camera, framed background, or whole-opening transform inside another Murali scene.
Run opening_scene_view for a complete perspective-opening-to-orthographic-content handoff:
cargo run --release --example opening_scene_view
Card​
A Card is a small rounded rectangle plus centered label. Use it for diagram nodes, callouts, dashboards, and explainer scenes where you want a reusable labeled box without manually adding a background and text every time.
use murali::frontend::collection::composite::Card;
let card = Card::new("Retrieved Context", 2.2, 0.58)
.with_radius(0.12)
.with_fill(Vec4::new(0.10, 0.14, 0.18, 0.72))
.with_stroke(0.025, Vec4::new(0.42, 0.86, 0.96, 0.9))
.with_text_style(0.18, Vec4::new(0.95, 0.98, 1.0, 1.0));
let ids = card.add_to_scene(&mut scene, Vec3::new(0.0, 0.0, 0.0));
add_to_scene returns CardIds:
ids.background; // rounded rectangle TattvaId
ids.label; // label TattvaId
ids.all(); // [background, label]
That makes cards easy to animate as a unit:
for id in ids.all() {
scene.hide_tattva(id);
timeline.animate(id).at(0.4).for_duration(0.5).appear().spawn();
}
timeline
.animate(ids.background)
.at(0.4)
.for_duration(0.6)
.draw()
.spawn();
timeline
.animate(ids.label)
.at(0.75)
.for_duration(0.35)
.typewrite_text()
.spawn();
Axes​
2D coordinate axes with tick marks.
use murali::frontend::collection::composite::axes::Axes;
let axes = Axes::new(x_range: (f32, f32), y_range: (f32, f32))
.with_step(1.0) // tick spacing on both axes
.with_thickness(0.03) // line thickness
.with_tick_size(0.16) // tick mark length
.with_color(Vec4)
.without_ticks(); // optional: hide ticks
scene.add_tattva(axes, Vec3::ZERO);
Fields can also be set directly:
let mut axes = Axes::new((-5.0, 5.0), (-3.0, 3.0));
axes.x_step = 1.0;
axes.y_step = 0.5;
axes.thickness = 0.03;
axes.tick_size = 0.18;
axes.color = Vec4::new(0.75, 0.79, 0.85, 1.0);
Projects to RenderPrimitive::Line segments — one for each axis and one per tick mark.
NumberPlane​
A full grid background with distinct axis and grid line colors.
use murali::frontend::collection::composite::number_plane::NumberPlane;
scene.add_tattva(
NumberPlane::new((-5.0, 5.0), (-3.5, 3.5))
.with_step(1.0),
Vec3::ZERO,
);
Default colors: grid lines are a muted grey, axis lines are brighter. Both are configurable via struct fields:
let mut plane = NumberPlane::new((-5.0, 5.0), (-3.5, 3.5));
plane.grid_color = Vec4::new(0.25, 0.28, 0.33, 1.0);
plane.axis_color = Vec4::new(0.78, 0.82, 0.88, 1.0);
plane.grid_thickness = 0.01;
plane.axis_thickness = 0.03;
Typically layered behind Axes and a graph:
scene.add_tattva(NumberPlane::new(...), Vec3::ZERO); // bottom
scene.add_tattva(Axes::new(...), Vec3::ZERO); // middle
scene.add_tattva(FunctionGraph::new(...), Vec3::ZERO); // top
Axes3D​
3D coordinate axes for use with parametric surfaces and 3D graphs.
use murali::frontend::collection::composite::axes3d::Axes3D;
scene.add_tattva(
Axes3D::new((-1.5, 1.5), (-1.5, 1.5), (-1.5, 1.5))
.with_step(0.5)
.with_axis_thickness(0.04),
Vec3::ZERO,
);
Layout helpers arrange transformed XY bounds. In perspective scenes, to_edge uses the camera
frame where it intersects the world z = 0 layout plane. Use manual 3D placement when content does
not live on that plane.