Merge pull request #6353 from Messhias/feat/g5-brush-lib-game-trails

G5: built-in Game Trails brush library
This commit is contained in:
BFlat
2026-11-11 02:53:07 +07:00
committed by GitHub
33 changed files with 207 additions and 19 deletions
+40 -2
View File
@@ -165,13 +165,13 @@ pub struct BuiltinBrushLibrary {
make: fn() -> BrushLibrary,
}
/// The built-in brush libraries, in menu order: original brushes generated in code, one library
/// per kind of brush.
/// The built-in brush libraries, in menu order: original brushes generated in code.
pub const BUILTIN: &[BuiltinBrushLibrary] = &[
BuiltinBrushLibrary { id: "art-ribbons", name: "Art Ribbons", make: art_ribbons },
BuiltinBrushLibrary { id: "calligraphic-nibs", name: "Calligraphic Nibs", make: calligraphic_nibs },
BuiltinBrushLibrary { id: "pattern-borders", name: "Pattern Borders", make: pattern_borders },
BuiltinBrushLibrary { id: "scatter-shapes", name: "Scatter Shapes", make: scatter_shapes },
BuiltinBrushLibrary { id: "game-trails", name: "Game Trails", make: game_trails },
];
/// Built-in library `id`, made once.
@@ -378,6 +378,44 @@ fn scatter_shapes() -> BrushLibrary {
built("Scatter Shapes", doc, brushes)
}
/// Game Trails: art brushes for motion lines and impact streaks (original game HUD / VFX art).
fn game_trails() -> BrushLibrary {
let speed = {
let n = 32;
let half = |x: f64| 2.2 * (1.0 - x / 100.0).powf(1.2).max(0.08);
let xs = |i: usize| 100.0 * i as f64 / n as f64;
let mut pts: Vec<Point> = (0..=n).map(xs).map(|x| Point::new(x, -half(x))).collect();
pts.extend((0..n).rev().map(xs).map(|x| Point::new(x, half(x))));
PathData::single(SubPath::polyline(&pts, true))
};
let dash = group(vec![
black(shapes::rounded_rectangle(Rect::new(0.0, -1.0, 14.0, 1.0), 1.0)),
black(shapes::rounded_rectangle(Rect::new(22.0, -1.0, 36.0, 1.0), 1.0)),
black(shapes::rounded_rectangle(Rect::new(44.0, -1.0, 58.0, 1.0), 1.0)),
black(shapes::rounded_rectangle(Rect::new(66.0, -1.0, 80.0, 1.0), 1.0)),
black(shapes::rounded_rectangle(Rect::new(88.0, -1.0, 100.0, 1.0), 1.0)),
]);
let burst = {
// A wedge that flares from a tip at x=0 to a wide end at x=100.
let tip = Point::new(0.0, 0.0);
let top = Point::new(100.0, -6.0);
let bot = Point::new(100.0, 6.0);
PathData::single(SubPath::polyline(&[tip, top, bot], true))
};
let zigzag = band(100.0, 1.8, 40, |x| {
let t = (x / 12.5).floor();
let local = (x / 12.5) - t;
if (t as i32) % 2 == 0 { -3.0 + 6.0 * local } else { 3.0 - 6.0 * local }
});
let brushes = vec![
art("Speed Line", black(speed), Colorization::Tints),
art("Dash Trail", dash, Colorization::Tints),
art("Burst Ray", black(burst), Colorization::Tints),
art("Zigzag", black(zigzag), Colorization::Tints),
];
built("Game Trails", Document::new(100.0, 100.0), brushes)
}
// ---------- commands ----------
fn builtin_info(b: &BuiltinBrushLibrary) -> LibraryInfo {
+14 -2
View File
@@ -104,7 +104,7 @@ fn the_builtin_libraries_are_original_brushes_generated_in_code() {
let list = run(&mut s, "brush.libraries", json!({}));
let libs = list["libraries"].as_array().unwrap();
let ids: Vec<&str> = libs.iter().map(|l| l["id"].as_str().unwrap()).collect();
assert_eq!(ids, ["art-ribbons", "calligraphic-nibs", "pattern-borders", "scatter-shapes"]);
assert_eq!(ids, ["art-ribbons", "calligraphic-nibs", "pattern-borders", "scatter-shapes", "game-trails"]);
assert!(libs.iter().all(|l| l["category"] == "builtIn"));
let lib = run(&mut s, "brush.library", json!({"library": "art ribbons"}));
assert_eq!(
@@ -112,8 +112,20 @@ fn the_builtin_libraries_are_original_brushes_generated_in_code() {
["Notched Ribbon", "Double Line", "Wave", "Teardrop", "Striped Ribbon"]
);
assert_eq!(libs[0]["count"].as_u64(), Some(5));
let trails = run(&mut s, "brush.library", json!({"library": "game-trails"}));
assert_eq!(
trails["brushes"].as_array().unwrap().iter().map(|b| b["name"].as_str().unwrap()).collect::<Vec<_>>(),
["Speed Line", "Dash Trail", "Burst Ray", "Zigzag"]
);
assert_eq!(libs[4]["count"].as_u64(), Some(4));
// One kind of brush per library, every brush drawing something.
for (id, kind) in [("art-ribbons", "art"), ("calligraphic-nibs", "calligraphic"), ("pattern-borders", "pattern"), ("scatter-shapes", "scatter")] {
for (id, kind) in [
("art-ribbons", "art"),
("calligraphic-nibs", "calligraphic"),
("pattern-borders", "pattern"),
("scatter-shapes", "scatter"),
("game-trails", "art"),
] {
let (_, lib) = cmd::brushlib::library(&s, id).unwrap();
assert!(lib.len() >= 4, "{id}");
for b in &lib.brushes {