use bevy::prelude::*;
use bevy_fontmesh::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "bevy_fontmesh showcase".to_string(),
resolution: bevy::window::WindowResolution::new(1280, 720),
..default()
}),
..default()
}))
.add_plugins(FontMeshPlugin::<StandardMaterial>::default())
.insert_resource(ClearColor(Color::BLACK))
.add_systems(Startup, setup)
.add_systems(
Update,
(assign_glyph_materials, animate_glyphs, orbit_camera),
)
.run();
}
#[derive(Component)]
struct OrbitCamera {
radius: f32,
angle: f32,
height: f32,
speed: f32,
}
#[derive(Component)]
struct CameraLight;
#[derive(Component)]
struct GlyphAnimated {
metal_index: usize,
}
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn(AmbientLight {
color: Color::srgb(0.9, 0.92, 1.0),
brightness: 800.0,
..default()
});
commands
.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 0.0, 6.0).looking_at(Vec3::ZERO, Vec3::Y),
OrbitCamera {
radius: 12.0,
angle: 0.0,
height: 0.5,
speed: 0.4,
},
))
.with_children(|parent| {
parent.spawn((
PointLight {
intensity: 200_000.0,
shadows_enabled: true,
color: Color::srgb(1.0, 0.95, 0.85),
range: 40.0,
..default()
},
Transform::from_xyz(3.0, 4.0, 2.0),
CameraLight,
));
parent.spawn((
PointLight {
intensity: 100_000.0,
shadows_enabled: false,
color: Color::srgb(0.9, 0.92, 1.0),
range: 40.0,
..default()
},
Transform::from_xyz(-4.0, 1.0, 2.0),
CameraLight,
));
parent.spawn((
PointLight {
intensity: 80_000.0,
shadows_enabled: false,
color: Color::srgb(1.0, 1.0, 1.0),
range: 40.0,
..default()
},
Transform::from_xyz(0.0, 6.0, 0.0),
CameraLight,
));
parent.spawn((
PointLight {
intensity: 60_000.0,
shadows_enabled: false,
color: Color::srgb(0.7, 0.8, 1.0),
range: 40.0,
..default()
},
Transform::from_xyz(0.0, 0.0, -8.0),
CameraLight,
));
});
commands.spawn(TextMeshGlyphsBundle {
text_glyphs: TextMeshGlyphs {
text: "BEVY".to_string(),
font: asset_server.load("fonts/Inter-Bold.otf"),
style: TextMeshStyle {
depth: 0.6,
subdivision: 64,
anchor: TextAnchor::Center,
..default()
},
},
material: MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::WHITE,
double_sided: true,
cull_mode: None,
..default()
})),
transform: Transform::from_scale(Vec3::splat(4.0)),
..default()
});
}
const METAL_COLORS: &[(Color, f32)] = &[
(Color::srgb(0.72, 0.45, 0.20), 0.20), (Color::srgb(0.91, 0.91, 0.91), 0.10), (Color::srgb(0.83, 0.69, 0.22), 0.15), (Color::srgb(0.95, 0.93, 0.88), 0.08), (Color::srgb(0.56, 0.57, 0.58), 0.25), (Color::srgb(0.72, 0.26, 0.05), 0.30), ];
fn assign_glyph_materials(
mut commands: Commands,
glyph_query: Query<(Entity, &GlyphMesh), Added<GlyphMesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
for (entity, glyph) in glyph_query.iter() {
let metal_index = glyph.char_index % METAL_COLORS.len();
let (color, roughness) = METAL_COLORS[metal_index];
commands.entity(entity).insert((
MeshMaterial3d(materials.add(StandardMaterial {
base_color: color,
metallic: 1.0,
perceptual_roughness: roughness,
reflectance: 1.0,
double_sided: true,
cull_mode: None,
..default()
})),
GlyphAnimated { metal_index },
));
}
}
fn animate_glyphs(
time: Res<Time>,
glyph_query: Query<(&GlyphAnimated, &MeshMaterial3d<StandardMaterial>)>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let t = time.elapsed_secs();
for (anim, mat_handle) in glyph_query.iter() {
let Some(mat) = materials.get_mut(mat_handle) else {
continue;
};
let phase = (t * 0.5 + anim.metal_index as f32 * 1.2).sin() * 0.5 + 0.5;
let next_index = (anim.metal_index + 1) % METAL_COLORS.len();
let (Color::Srgba(a), ra) = (
METAL_COLORS[anim.metal_index].0,
METAL_COLORS[anim.metal_index].1,
) else {
continue;
};
let (Color::Srgba(b), rb) = (METAL_COLORS[next_index].0, METAL_COLORS[next_index].1) else {
continue;
};
mat.base_color = Color::srgb(
a.red + (b.red - a.red) * phase,
a.green + (b.green - a.green) * phase,
a.blue + (b.blue - a.blue) * phase,
);
mat.perceptual_roughness = ra + (rb - ra) * phase;
}
}
fn orbit_camera(time: Res<Time>, mut query: Query<(&mut Transform, &mut OrbitCamera)>) {
for (mut transform, mut orbit) in query.iter_mut() {
orbit.angle += orbit.speed * time.delta_secs();
let x = orbit.angle.sin() * orbit.radius;
let z = orbit.angle.cos() * orbit.radius;
*transform = Transform::from_xyz(x, orbit.height, z).looking_at(Vec3::ZERO, Vec3::Y);
}
}