Files
Fyrox/examples/lightmap.rs
T

615 lines
25 KiB
Rust

//! Example 09. Lightmap.
//!
//! Difficulty: Easy.
//!
//! This example shows how to load simple scene made in [Fyroxed](https://github.com/FyroxEngine/Fyrox)
//! and generate lightmap for it. Lightmaps are still in active development and not meant to be used.
pub mod shared;
use crate::shared::create_camera;
use fyrox::resource::model::{Model, ModelResourceExtension};
use fyrox::{
asset::manager::ResourceManager,
core::{
algebra::{UnitQuaternion, Vector2, Vector3},
color::Color,
futures,
pool::Handle,
visitor::Visitor,
},
engine::{
Engine, EngineInitParams, GraphicsContext, GraphicsContextParams, SerializationContext,
},
event::{ElementState, Event, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
gui::{
button::{ButtonBuilder, ButtonMessage},
grid::{Column, GridBuilder, Row},
message::MessageDirection,
progress_bar::{ProgressBarBuilder, ProgressBarMessage},
stack_panel::StackPanelBuilder,
text::{TextBuilder, TextMessage},
widget::{WidgetBuilder, WidgetMessage},
window::{WindowBuilder, WindowMessage, WindowTitle},
BuildContext, HorizontalAlignment, Thickness, UiNode, VerticalAlignment,
},
scene::{light::BaseLight, node::Node, Scene, SceneLoader},
utils::{
lightmap::{CancellationToken, Lightmap, ProgressIndicator, ProgressStage},
log::{Log, MessageKind},
translate_event,
},
window::WindowAttributes,
};
use std::{
path::Path,
sync::{Arc, Mutex},
time::Instant,
};
const LIGHTMAP_SCENE_PATH: &str = "examples/data/lightmap_scene.rgs";
struct Interface {
root: Handle<UiNode>,
debug_text: Handle<UiNode>,
progress_bar: Handle<UiNode>,
progress_text: Handle<UiNode>,
cancel: Handle<UiNode>,
progress_grid: Handle<UiNode>,
choice_window: Handle<UiNode>,
load_existing: Handle<UiNode>,
generate_new: Handle<UiNode>,
}
fn create_ui(ctx: &mut BuildContext, screen_size: Vector2<f32>) -> Interface {
let debug_text;
let progress_bar;
let progress_text;
let cancel;
let progress_grid;
let root = GridBuilder::new(
WidgetBuilder::new()
.with_width(screen_size.x)
.with_height(screen_size.y)
.with_child({
debug_text =
TextBuilder::new(WidgetBuilder::new().on_row(0).on_column(0)).build(ctx);
debug_text
})
.with_child({
progress_grid = GridBuilder::new(
WidgetBuilder::new()
.with_visibility(!Path::new(LIGHTMAP_SCENE_PATH).exists())
.on_column(1)
.on_row(1)
.with_child({
progress_bar = ProgressBarBuilder::new(
WidgetBuilder::new().on_row(1).on_column(0),
)
.build(ctx);
progress_bar
})
.with_child({
progress_text = TextBuilder::new(
WidgetBuilder::new()
.on_column(0)
.on_row(0)
.with_margin(Thickness::bottom(20.0))
.with_vertical_alignment(VerticalAlignment::Bottom),
)
.with_horizontal_text_alignment(HorizontalAlignment::Center)
.build(ctx);
progress_text
})
.with_child(
StackPanelBuilder::new(
WidgetBuilder::new()
.on_column(0)
.on_row(2)
.with_margin(Thickness::uniform(4.0))
.with_child({
cancel = ButtonBuilder::new(
WidgetBuilder::new()
.with_width(100.0)
.with_height(30.0),
)
.with_text("Cancel")
.build(ctx);
cancel
}),
)
.build(ctx),
),
)
.add_column(Column::stretch())
.add_row(Row::stretch())
.add_row(Row::strict(25.0))
.add_row(Row::stretch())
.build(ctx);
progress_grid
}),
)
.add_row(Row::stretch())
.add_row(Row::stretch())
.add_row(Row::stretch())
.add_column(Column::stretch())
.add_column(Column::strict(500.0))
.add_column(Column::stretch())
.build(ctx);
let load_existing;
let generate_new;
let choice_window = WindowBuilder::new(WidgetBuilder::new().with_width(300.0))
.open(false)
.can_close(false)
.with_title(WindowTitle::Text("Select Action".to_owned()))
.with_content(
GridBuilder::new(
WidgetBuilder::new()
.with_child({
load_existing = ButtonBuilder::new(
WidgetBuilder::new()
.on_column(0)
.with_margin(Thickness::uniform(1.0)),
)
.with_text("Load Existing")
.build(ctx);
load_existing
})
.with_child({
generate_new = ButtonBuilder::new(
WidgetBuilder::new()
.on_column(1)
.with_margin(Thickness::uniform(1.0)),
)
.with_text("Generate New")
.build(ctx);
generate_new
}),
)
.add_row(Row::stretch())
.add_column(Column::stretch())
.add_column(Column::stretch())
.build(ctx),
)
.build(ctx);
Interface {
root,
debug_text,
progress_bar,
progress_text,
cancel,
choice_window,
load_existing,
generate_new,
progress_grid,
}
}
struct GameScene {
scene: Scene,
root: Handle<Node>,
}
struct SceneLoadContext {
data: Option<GameScene>,
progress_indicator: ProgressIndicator,
cancellation_token: CancellationToken,
start_time: Instant,
generate_lightmap: bool,
}
fn create_scene_async(
serialization_context: Arc<SerializationContext>,
resource_manager: ResourceManager,
generate_lightmap: bool,
) -> Arc<Mutex<SceneLoadContext>> {
let progress_indicator = ProgressIndicator::new();
let cancellation_token = CancellationToken::new();
// Create load context - it will be shared with caller and loader threads.
let context = Arc::new(Mutex::new(SceneLoadContext {
data: None,
progress_indicator: progress_indicator.clone(),
cancellation_token: cancellation_token.clone(),
start_time: Instant::now(),
generate_lightmap,
}));
let result = context.clone();
// Spawn separate thread which will create scene by loading various assets.
std::thread::spawn(move || {
futures::executor::block_on(async move {
if generate_lightmap {
let mut scene = Scene::new();
// Set ambient light.
scene.ambient_lighting_color = Color::opaque(80, 80, 80);
// Camera is our eyes in the world - you won't see anything without it.
create_camera(
resource_manager.clone(),
Vector3::new(5.0, 4.0, -8.0),
&mut scene.graph,
)
.await;
// There is no difference between scene created in Fyroxed and any other
// model file, so any scene can be used directly as resource.
let root = resource_manager
.request::<Model, _>("examples/data/sponza/Sponza.rgs")
.await
.unwrap()
.instantiate(&mut scene);
if let Ok(lightmap) = Lightmap::new(
&mut scene,
64,
|_, _| true,
cancellation_token,
progress_indicator,
) {
lightmap
.save("examples/data/lightmaps/", resource_manager)
.unwrap();
scene.set_lightmap(lightmap).unwrap();
for node in scene.graph.linear_iter_mut() {
if node.query_component_ref::<BaseLight>().is_some() {
node.set_visibility(false);
}
}
let mut visitor = Visitor::new();
scene.save("Scene", &mut visitor).unwrap();
visitor.save_binary(LIGHTMAP_SCENE_PATH).unwrap();
context.lock().unwrap().data = Some(GameScene { scene, root });
}
} else {
let scene = SceneLoader::from_file(
LIGHTMAP_SCENE_PATH,
serialization_context,
resource_manager.clone(),
)
.await
.unwrap()
.finish()
.await;
let root = scene.graph[scene.graph.get_root()].children()[0];
context.lock().unwrap().data = Some(GameScene { scene, root });
}
})
});
// Immediately return shared context.
result
}
struct InputController {
rotate_left: bool,
rotate_right: bool,
}
fn main() {
let event_loop = EventLoop::new();
let graphics_context_params = GraphicsContextParams {
window_attributes: WindowAttributes {
title: "Example - Lightmap".to_string(),
resizable: true,
..Default::default()
},
vsync: true,
};
let serialization_context = Arc::new(SerializationContext::new());
let mut engine = Engine::new(EngineInitParams {
graphics_context_params,
resource_manager: ResourceManager::new(),
serialization_context,
})
.unwrap();
// Create simple user interface that will show some useful info.
let interface = create_ui(
&mut engine.user_interface.build_ctx(),
Vector2::new(100.0, 100.0),
);
let mut game_scene = if Path::new(LIGHTMAP_SCENE_PATH).exists() {
engine
.user_interface
.send_message(WindowMessage::open_modal(
interface.choice_window,
MessageDirection::ToWidget,
true,
));
Arc::new(Mutex::new(SceneLoadContext {
data: None,
progress_indicator: ProgressIndicator::new(),
cancellation_token: CancellationToken::new(),
start_time: Instant::now(),
generate_lightmap: false,
}))
} else {
create_scene_async(
engine.serialization_context.clone(),
engine.resource_manager.clone(),
true,
)
};
// Initially these handles are None, once scene is loaded they'll be assigned.
let mut scene_handle = Handle::NONE;
let mut model_handle = Handle::NONE;
let mut previous = Instant::now();
let fixed_timestep = 1.0 / 60.0;
let mut lag = 0.0;
// We will rotate model using keyboard input.
let mut model_angle = 180.0f32.to_radians();
// Create input controller - it will hold information about needed actions.
let mut input_controller = InputController {
rotate_left: false,
rotate_right: false,
};
// Finally run our event loop which will respond to OS and window events and update
// engine state accordingly. Engine lets you to decide which event should be handled,
// this is minimal working example if how it should be.
event_loop.run(move |event, window_target, control_flow| {
match event {
Event::MainEventsCleared => {
// This main game loop - it has fixed time step which means that game
// code will run at fixed speed even if renderer can't give you desired
// 60 fps.
let elapsed = previous.elapsed();
previous = Instant::now();
lag += elapsed.as_secs_f32();
while lag >= fixed_timestep {
// ************************
// Put your game logic here.
// ************************
// Check each frame if our scene is created - here we just trying to lock context
// without blocking, it is important for main thread to be functional while other
// thread still loading data.
if let Ok(mut load_context) = game_scene.try_lock() {
if let Some(game_scene) = load_context.data.take() {
// Add scene to engine - engine will take ownership over scene and will return
// you a handle to scene which can be used later on to borrow it and do some
// actions you need.
scene_handle = engine.scenes.add(game_scene.scene);
model_handle = game_scene.root;
// Once scene is loaded, we should hide progress bar and text.
engine
.user_interface
.send_message(WidgetMessage::visibility(
interface.progress_grid,
MessageDirection::ToWidget,
false,
));
engine.user_interface.send_message(WindowMessage::close(
interface.choice_window,
MessageDirection::ToWidget,
));
}
let stage = match load_context.progress_indicator.stage() {
ProgressStage::LightsCaching => "Caching Lights",
ProgressStage::UvGeneration => "Generating UVs",
ProgressStage::GeometryCaching => "Caching Geometry",
ProgressStage::CalculatingLight => "Calculating Light",
};
let message = if load_context.generate_lightmap {
format!(
"Please wait until lightmap is fully generated.\n\
Stage {} of 4: {}\n\
Elapsed time: {:.2} s",
load_context.progress_indicator.stage() as u32 + 1,
stage,
load_context.start_time.elapsed().as_secs_f32(),
)
} else {
format!(
"Please wait until existing lightmap is fully loaded.\n\
Elapsed time: {:.2} s",
load_context.start_time.elapsed().as_secs_f32(),
)
};
// Report progress in UI.
engine
.user_interface
.send_message(ProgressBarMessage::progress(
interface.progress_bar,
MessageDirection::ToWidget,
load_context.progress_indicator.progress_percent() as f32 / 100.0,
));
engine.user_interface.send_message(TextMessage::text(
interface.progress_text,
MessageDirection::ToWidget,
message,
));
}
// Update scene only if it is loaded.
if scene_handle.is_some() {
// Use stored scene handle to borrow a mutable reference of scene in
// engine.
let scene = &mut engine.scenes[scene_handle];
// Rotate model according to input controller state.
if input_controller.rotate_left {
model_angle -= 5.0f32.to_radians();
} else if input_controller.rotate_right {
model_angle += 5.0f32.to_radians();
}
scene.graph[model_handle]
.local_transform_mut()
.set_rotation(UnitQuaternion::from_axis_angle(
&Vector3::y_axis(),
model_angle,
));
}
// While scene is loading, we will update progress bar.
if let GraphicsContext::Initialized(ref ctx) = engine.graphics_context {
let debug_text = format!(
"Example 09 - Lightmap\nUse [A][D] keys to rotate model.\n{}",
ctx.renderer.get_statistics()
);
engine.user_interface.send_message(TextMessage::text(
interface.debug_text,
MessageDirection::ToWidget,
debug_text,
));
}
while let Some(ui_event) = engine.user_interface.poll_message() {
if let Some(ButtonMessage::Click) = ui_event.data::<ButtonMessage>() {
if ui_event.destination() == interface.cancel {
game_scene.lock().unwrap().cancellation_token.cancel();
engine
.user_interface
.send_message(WidgetMessage::visibility(
interface.progress_grid,
MessageDirection::ToWidget,
false,
));
engine
.user_interface
.send_message(WindowMessage::open_modal(
interface.choice_window,
MessageDirection::ToWidget,
true,
));
} else if ui_event.destination() == interface.generate_new {
game_scene = create_scene_async(
engine.serialization_context.clone(),
engine.resource_manager.clone(),
true,
);
engine.user_interface.send_message(WindowMessage::close(
interface.choice_window,
MessageDirection::ToWidget,
));
engine
.user_interface
.send_message(WidgetMessage::visibility(
interface.progress_grid,
MessageDirection::ToWidget,
true,
));
} else if ui_event.destination() == interface.load_existing {
game_scene = create_scene_async(
engine.serialization_context.clone(),
engine.resource_manager.clone(),
false,
);
engine.user_interface.send_message(WindowMessage::close(
interface.choice_window,
MessageDirection::ToWidget,
));
engine
.user_interface
.send_message(WidgetMessage::visibility(
interface.progress_grid,
MessageDirection::ToWidget,
true,
));
}
}
}
engine.update(fixed_timestep, control_flow, &mut lag, Default::default());
lag -= fixed_timestep;
}
// Rendering must be explicitly requested and handled after RedrawRequested event is received.
if let GraphicsContext::Initialized(ref ctx) = engine.graphics_context {
ctx.window.request_redraw();
}
}
Event::Resumed => {
engine.initialize_graphics_context(window_target).unwrap();
}
Event::Suspended => {
engine.destroy_graphics_context().unwrap();
}
Event::RedrawRequested(_) => {
// Run renderer at max speed - it is not tied to game code.
engine.render().unwrap();
}
Event::WindowEvent { event, .. } => {
match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::Resized(size) => {
// It is very important to handle Resized event from window, because
// renderer knows nothing about window size - it must be notified
// directly when window size has changed.
if let Err(e) = engine.set_frame_size(size.into()) {
Log::writeln(
MessageKind::Error,
format!("Unable to set frame size: {:?}", e),
);
}
// Root UI node should be resized too, otherwise progress bar will stay
// in wrong position after resize.
if let GraphicsContext::Initialized(ref ctx) = engine.graphics_context {
let size = size.to_logical(ctx.window.scale_factor());
engine.user_interface.send_message(WidgetMessage::width(
interface.root,
MessageDirection::ToWidget,
size.width,
));
engine.user_interface.send_message(WidgetMessage::height(
interface.root,
MessageDirection::ToWidget,
size.height,
));
}
}
WindowEvent::KeyboardInput { input, .. } => {
// Handle key input events via `WindowEvent`, not via `DeviceEvent` (#32)
if let Some(key_code) = input.virtual_keycode {
match key_code {
VirtualKeyCode::A => {
input_controller.rotate_left =
input.state == ElementState::Pressed
}
VirtualKeyCode::D => {
input_controller.rotate_right =
input.state == ElementState::Pressed
}
_ => (),
}
}
}
_ => (),
}
// It is very important to "feed" user interface (UI) with events coming
// from main window, otherwise UI won't respond to mouse, keyboard, or any
// other event.
if let Some(os_event) = translate_event(&event) {
engine.user_interface.process_os_event(&os_event);
}
}
Event::DeviceEvent { .. } => {
// Handle key input events via `WindowEvent`, not via `DeviceEvent` (#32)
}
_ => *control_flow = ControlFlow::Poll,
}
});
}