diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 46e675a..d795360 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -24,7 +24,7 @@ { "type": "cargo", "command": "run", - "args": ["--bin", "worldspace"], + "args": ["--bin", "worldspace_text"], "problemMatcher": [ "$rustc" ], diff --git a/examples/worldspace_text/Cargo.toml b/examples/worldspace_text/Cargo.toml new file mode 100644 index 0000000..aa398c0 --- /dev/null +++ b/examples/worldspace_text/Cargo.toml @@ -0,0 +1,25 @@ +[package] + name = "worldspace_text" + authors.workspace = true + version.workspace = true + edition.workspace = true + publish = false + +[dependencies] + bevy = { version = "^0.14.0", default_features = false, features = [ + "bevy_asset", + "bevy_winit", + "bevy_core_pipeline", + "bevy_pbr", + "bevy_render", + "bevy_sprite", + "bevy_text", + "multi_threaded", + "png", + "hdr", + "vorbis", + "x11", + "tonemapping_luts", + ] } + bevy_lunex = { workspace = true, features=["debug"] } + bevy_mod_billboard = "^0.7.0" diff --git a/examples/worldspace_text/assets/header.png b/examples/worldspace_text/assets/header.png new file mode 100644 index 0000000..4e1e34d Binary files /dev/null and b/examples/worldspace_text/assets/header.png differ diff --git a/examples/worldspace_text/assets/panel.png b/examples/worldspace_text/assets/panel.png new file mode 100644 index 0000000..18579d3 Binary files /dev/null and b/examples/worldspace_text/assets/panel.png differ diff --git a/examples/worldspace_text/assets/rajdhani/OFL.txt b/examples/worldspace_text/assets/rajdhani/OFL.txt new file mode 100644 index 0000000..79115a5 --- /dev/null +++ b/examples/worldspace_text/assets/rajdhani/OFL.txt @@ -0,0 +1,93 @@ +Copyright (c) 2014, Indian Type Foundry (info@indiantypefoundry.com). + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/examples/worldspace_text/assets/rajdhani/Rajdhani-Bold.ttf b/examples/worldspace_text/assets/rajdhani/Rajdhani-Bold.ttf new file mode 100644 index 0000000..4259153 Binary files /dev/null and b/examples/worldspace_text/assets/rajdhani/Rajdhani-Bold.ttf differ diff --git a/examples/worldspace_text/assets/rajdhani/Rajdhani-Light.ttf b/examples/worldspace_text/assets/rajdhani/Rajdhani-Light.ttf new file mode 100644 index 0000000..2d6b269 Binary files /dev/null and b/examples/worldspace_text/assets/rajdhani/Rajdhani-Light.ttf differ diff --git a/examples/worldspace_text/assets/rajdhani/Rajdhani-Medium.ttf b/examples/worldspace_text/assets/rajdhani/Rajdhani-Medium.ttf new file mode 100644 index 0000000..2e82d0a Binary files /dev/null and b/examples/worldspace_text/assets/rajdhani/Rajdhani-Medium.ttf differ diff --git a/examples/worldspace_text/assets/rajdhani/Rajdhani-Regular.ttf b/examples/worldspace_text/assets/rajdhani/Rajdhani-Regular.ttf new file mode 100644 index 0000000..5618bc3 Binary files /dev/null and b/examples/worldspace_text/assets/rajdhani/Rajdhani-Regular.ttf differ diff --git a/examples/worldspace_text/assets/rajdhani/Rajdhani-SemiBold.ttf b/examples/worldspace_text/assets/rajdhani/Rajdhani-SemiBold.ttf new file mode 100644 index 0000000..8738ec5 Binary files /dev/null and b/examples/worldspace_text/assets/rajdhani/Rajdhani-SemiBold.ttf differ diff --git a/examples/worldspace_text/src/boilerplate.rs b/examples/worldspace_text/src/boilerplate.rs new file mode 100644 index 0000000..ce168ac --- /dev/null +++ b/examples/worldspace_text/src/boilerplate.rs @@ -0,0 +1,80 @@ +use bevy::{app::PluginGroupBuilder, input::mouse::{MouseMotion, MouseWheel}, prelude::*, render::settings::WgpuSettings}; + + +// #========================================# +// #=== BOILERPLATE REQUIRED FOR BEVYCOM ===# + +#[derive(Component)] +pub struct PlayerCam { + pub orbit: Vec3, + pub distance: f32, + pub sensitivity: Vec2, +} +pub fn rotate_playercam(mut mouse_motion_events: EventReader, mouse_input: Res>, mut query: Query<(&PlayerCam, &mut Transform)>) { + let mut delta = Vec2::ZERO; + if mouse_input.pressed(MouseButton::Left) { + delta = mouse_motion_events.read().map(|e| e.delta).sum(); + } + if mouse_input.just_pressed(MouseButton::Left) { + delta = Vec2::ZERO; + } + for (camera, mut transform) in &mut query { + + // ROTATION + let (mut rx, mut ry, rz) = transform.rotation.to_euler(EulerRot::YXZ); + rx += (-delta.x * camera.sensitivity.x).to_radians(); + ry += (-delta.y * camera.sensitivity.x).to_radians(); + ry = ry.clamp(-90_f32.to_radians(), 90_f32.to_radians()); + transform.rotation = Quat::from_euler(EulerRot::YXZ, rx, ry, rz); + + + // ORBIT TRANSFORM + let tx = camera.distance * rx.sin(); + let ty = camera.distance * rx.cos(); + let tz = camera.distance * ry.sin(); + + let diff = camera.distance * ry.cos(); + let plane_ratio_decrease = (camera.distance - diff)/camera.distance; + + transform.translation = camera.orbit; + transform.translation.x += tx * (1.0 - plane_ratio_decrease); + transform.translation.z += ty * (1.0 - plane_ratio_decrease); + transform.translation.y += -tz; + } +} +pub fn zoom_playercam(mut mouse_wheel_events: EventReader, mut query: Query<&mut PlayerCam>) { + let delta: f32 = mouse_wheel_events.read().map(|e| e.y).sum(); + for mut camera in &mut query { + camera.distance += -delta * camera.distance/25.0; + } +} + + +// #======================================# +// #=== JUST SPAWN PRESETS FOR CLARITY ===# + +/// Function to return default plugins with correct settings +pub fn default_plugins() -> PluginGroupBuilder { + DefaultPlugins.set ( + WindowPlugin { + primary_window: Some(Window { + title: "Worldspace".into(), + mode: bevy::window::WindowMode::Windowed, + present_mode: bevy::window::PresentMode::AutoNoVsync, + resolution: bevy::window::WindowResolution::new(1280.0, 720.0), + ..default() + }), + ..default() + } + ).set ( + bevy::render::RenderPlugin { + render_creation: bevy::render::settings::RenderCreation::Automatic( + WgpuSettings { + power_preference: bevy::render::settings::PowerPreference::HighPerformance, + ..default() + } + ), + ..default() + } + ) +} diff --git a/examples/worldspace_text/src/main.rs b/examples/worldspace_text/src/main.rs new file mode 100644 index 0000000..8bda258 --- /dev/null +++ b/examples/worldspace_text/src/main.rs @@ -0,0 +1,220 @@ +use bevy::prelude::*; +use bevy::sprite::Anchor; +use bevy_lunex::prelude::*; +use bevy_mod_billboard::prelude::*; +use bevy_mod_billboard::BillboardLockAxis; + +mod boilerplate; +use boilerplate::*; + +// # ABOUT THIS EXAMPLE +// This is a WIP progress, prototype example, that utilises bevy_mod_billboard for 3D text. +// Everything here is a subject of change, and this implementation will be changed in the +// future. I wish to integrate the crate upstream, but due to me being quite busy, my +// progress is quite slow. In the meantime, this is an example on how to do it yourself. + +fn main() { + App::new() + .add_plugins((default_plugins(), UiPlugin, BillboardPlugin)) + .add_systems(Startup, setup) + .add_systems(Update, (rotate_playercam, zoom_playercam)) + .run(); +} + +fn setup( + mut commands: Commands, + mut material: ResMut>, + asset_server: Res, +) { + // Spawn cursor + commands.spawn(CursorBundle::default()); + + // Spawn camera + commands.spawn(( + Camera3dBundle::default(), + PlayerCam { + orbit: Vec3::new(0.0, 0.0, 0.0), + distance: 2.0, + sensitivity: Vec2::splat(0.1), + } + )); + + // Spawn it 3 times + for x in [0] { + + // Spawn the floating Ui panel + commands.spawn(( + UiTreeBundle:: { + transform: Transform::from_xyz(-0.4, 0.0, 0.0 + (0.3 * x as f32)), + tree: UiTree::new3d("HeaderWidget"), + ..default() + }, + )).with_children(|ui| { + ui.spawn(( + // Link this widget + UiLink::::path("Root"), + + // The layout that is used when in base state + UiLayout::window_full().size((818.0, 170.0)).pack::(), + + // Give the mesh an image + UiMaterial3dBundle::from_image(&mut material, asset_server.load("header.png")), + + // Make the panel pickable + PickableBundle::default(), + + // This is required to control our hover animation + UiAnimator::::new().forward_speed(6.0).backward_speed(5.0), + + // This is required for Layout animation + UiLayoutController::default(), + + // The layout that is used when in hover state + UiLayout::window_full().x(100.0).size((818.0, 170.0)).pack::(), + + // This will change cursor icon on mouse hover + OnHoverSetCursor::new(CursorIcon::Pointer), + )); + + ui.spawn(( + // Link this widget + UiLink::::path("Root/Name"), + + // The layout that is used when in base state + UiLayout::window().anchor(Anchor::CenterLeft).pos(Rl((20.0, 40.0))).pack::(), + + // Pop the text further out + UiDepthBias(20.0), + + // Add the 3D text + BillboardTextBundle { + transform: Transform::from_scale(Vec3::splat(0.00085)).looking_at(Vec3::new(0.0, 0.0, 1.0), Vec3::Y), + text: Text::from_sections([ + TextSection { + value: "SQ. JANE KELLY".to_string(), + style: TextStyle { + font_size: 60.0, + font: asset_server.load("rajdhani/Rajdhani-SemiBold.ttf"), + color: Color::Srgba(Srgba::rgb_u8(129, 192, 205)), + }, + }, + ]) + .with_justify(JustifyText::Left), + text_anchor: Anchor::CenterLeft, + ..default() + }, + + // Lock the billboard automatic rotation + BillboardLockAxis { + rotation: true, + ..default() + }, + )); + + ui.spawn(( + // Link this widget + UiLink::::path("Root/Missions"), + + // The layout that is used when in base state + UiLayout::window().anchor(Anchor::CenterLeft).pos(Rl((35.0, 77.0))).pack::(), + + // Pop the text further out + UiDepthBias(20.0), + + // Add the 3D text + BillboardTextBundle { + transform: Transform::from_scale(Vec3::splat(0.00085)).looking_at(Vec3::new(0.0, 0.0, 1.0), Vec3::Y), + text: Text::from_sections([ + TextSection { + value: "6".to_string(), + style: TextStyle { + font_size: 60.0, + font: asset_server.load("rajdhani/Rajdhani-SemiBold.ttf"), + color: Color::Srgba(Srgba::rgb_u8(129, 192, 205)), + }, + }, + ]) + .with_justify(JustifyText::Left), + text_anchor: Anchor::CenterLeft, + ..default() + }, + + // Lock the billboard automatic rotation + BillboardLockAxis { + rotation: true, + ..default() + }, + )); + + ui.spawn(( + // Link this widget + UiLink::::path("Root/Kills"), + + // The layout that is used when in base state + UiLayout::window().anchor(Anchor::CenterLeft).pos(Rl((55.0, 77.0))).pack::(), + + // Pop the text further out + UiDepthBias(20.0), + + // Add the 3D text + BillboardTextBundle { + transform: Transform::from_scale(Vec3::splat(0.00085)).looking_at(Vec3::new(0.0, 0.0, 1.0), Vec3::Y), + text: Text::from_sections([ + TextSection { + value: "17".to_string(), + style: TextStyle { + font_size: 60.0, + font: asset_server.load("rajdhani/Rajdhani-SemiBold.ttf"), + color: Color::Srgba(Srgba::rgb_u8(129, 192, 205)), + }, + }, + ]) + .with_justify(JustifyText::Left), + text_anchor: Anchor::CenterLeft, + ..default() + }, + + // Lock the billboard automatic rotation + BillboardLockAxis { + rotation: true, + ..default() + }, + )); + + ui.spawn(( + // Link this widget + UiLink::::path("Root/Status"), + + // The layout that is used when in base state + UiLayout::window().anchor(Anchor::CenterLeft).pos(Rl((74.0, 77.0))).pack::(), + + // Pop the text further out + UiDepthBias(20.0), + + // Add the 3D text + BillboardTextBundle { + transform: Transform::from_scale(Vec3::splat(0.0007)).looking_at(Vec3::new(0.0, 0.0, 1.0), Vec3::Y), + text: Text::from_sections([ + TextSection { + value: "ON MISSION".to_string(), + style: TextStyle { + font_size: 60.0, + font: asset_server.load("rajdhani/Rajdhani-Regular.ttf"), + color: Color::Srgba(Srgba::rgb_u8(129, 192, 205)), + }, + }, + ]) + .with_justify(JustifyText::Left), + text_anchor: Anchor::CenterLeft, + ..default() + }, + + // Lock the billboard automatic rotation + BillboardLockAxis { + rotation: true, + ..default() + }, + )); + }); + } +}