use std::collections::HashSet;
use std::sync::Arc;
use glm::{Mat4, Vec3};
use nalgebra_glm as glm;
use nightshade_ecs::Entity;
use nightshade_ecs::dynamic::DynWorld;
use nightshade_renderer::config::{
EffectiveShading, RenderLight, RenderLightData, RenderSettings, RendererState,
};
use nightshade_renderer::mesh_cache::MeshCache;
use nightshade_renderer::render_world::{Transform, register_render_components};
use nightshade_renderer::wgpu::WgpuRenderer;
use nightshade_renderer::wgpu::frame::render_frame;
use nightshade_renderer::wgpu::presentation::install_presentation_passes;
#[cfg(feature = "screenshot")]
use nightshade_renderer::wgpu::presentation::{poll_screenshot_readback, request_screenshot_copy};
use nightshade_renderer::wgpu::render_configs::{
CameraFrameInputs, CameraProjectionParams, SingleViewHost, ViewFrame, compose_single_view,
restore_single_view,
};
use nightshade_renderer::wgpu::texture_cache::TextureCache;
use winit::application::ApplicationHandler;
use winit::event::{ElementState, KeyEvent, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::keyboard::{KeyCode, PhysicalKey};
use winit::window::{Window, WindowId};
const INITIAL_WIDTH: u32 = 1280;
const INITIAL_HEIGHT: u32 = 720;
const CAMERA_ENTITY: Entity = Entity {
id: 0,
generation: 0,
};
pub struct Camera {
pub position: Vec3,
pub yaw: f32,
pub pitch: f32,
pub y_fov_rad: f32,
pub z_near: f32,
pub z_far: f32,
pub move_speed: f32,
pub look_speed: f32,
}
impl Default for Camera {
fn default() -> Self {
Self {
position: Vec3::new(0.0, 1.5, 6.0),
yaw: 0.0,
pitch: -0.15,
y_fov_rad: std::f32::consts::FRAC_PI_4,
z_near: 0.1,
z_far: 1000.0,
move_speed: 6.0,
look_speed: 1.5,
}
}
}
fn camera_forward(camera: &Camera) -> Vec3 {
Vec3::new(
camera.pitch.cos() * camera.yaw.sin(),
camera.pitch.sin(),
-camera.pitch.cos() * camera.yaw.cos(),
)
}
fn camera_view(camera: &Camera) -> Mat4 {
let target = camera.position + camera_forward(camera);
glm::look_at(&camera.position, &target, &Vec3::new(0.0, 1.0, 0.0))
}
fn camera_advance(camera: &mut Camera, delta_time: f32, keys: &HashSet<KeyCode>) {
let mut look = 0.0;
let mut pitch = 0.0;
if keys.contains(&KeyCode::ArrowLeft) {
look += 1.0;
}
if keys.contains(&KeyCode::ArrowRight) {
look -= 1.0;
}
if keys.contains(&KeyCode::ArrowUp) {
pitch += 1.0;
}
if keys.contains(&KeyCode::ArrowDown) {
pitch -= 1.0;
}
camera.yaw += look * camera.look_speed * delta_time;
camera.pitch = (camera.pitch + pitch * camera.look_speed * delta_time).clamp(
-std::f32::consts::FRAC_PI_2 + 0.01,
std::f32::consts::FRAC_PI_2 - 0.01,
);
let forward = camera_forward(camera);
let right = forward.cross(&Vec3::new(0.0, 1.0, 0.0)).normalize();
let mut motion = Vec3::zeros();
if keys.contains(&KeyCode::KeyW) {
motion += forward;
}
if keys.contains(&KeyCode::KeyS) {
motion -= forward;
}
if keys.contains(&KeyCode::KeyD) {
motion += right;
}
if keys.contains(&KeyCode::KeyA) {
motion -= right;
}
if keys.contains(&KeyCode::KeyE) {
motion += Vec3::new(0.0, 1.0, 0.0);
}
if keys.contains(&KeyCode::KeyQ) {
motion -= Vec3::new(0.0, 1.0, 0.0);
}
if motion.norm() > 0.0 {
camera.position += motion.normalize() * camera.move_speed * delta_time;
}
}
pub fn directional_light(
direction: Vec3,
color: Vec3,
intensity: f32,
) -> (RenderLightData, Transform) {
let light = RenderLight::directional(CAMERA_ENTITY, direction, color, intensity);
(light.light, Transform(light.transform))
}
fn new_host() -> SingleViewHost {
SingleViewHost {
scene_world: DynWorld::from_registry(register_render_components()),
state: RendererState {
occlusion_culling_enabled: false,
..RendererState::default()
},
mesh_cache: MeshCache::default(),
texture_cache: TextureCache::default(),
settings: RenderSettings::default(),
debug_draw: Default::default(),
}
}
pub trait Example {
type State;
fn setup(host: &mut SingleViewHost, camera: &mut Camera) -> Self::State;
fn update(_: &mut SingleViewHost, _: &mut Camera, _: &mut Self::State, _: f32) {}
}
struct Harness<E: Example> {
title: String,
window: Option<Arc<Window>>,
renderer: Option<WgpuRenderer>,
host: SingleViewHost,
camera: Camera,
state: Option<E::State>,
keys: HashSet<KeyCode>,
last_frame: std::time::Instant,
time: f32,
shot: Option<std::path::PathBuf>,
shot_frames: u32,
}
impl<E: Example> Harness<E> {
fn frame(&mut self, width: u32, height: u32, delta_time: f32) {
self.time += delta_time;
camera_advance(&mut self.camera, delta_time, &self.keys);
if let Some(state) = self.state.as_mut() {
E::update(&mut self.host, &mut self.camera, state, self.time);
}
let aspect = width as f32 / height.max(1) as f32;
let camera = CameraFrameInputs::perspective(
CAMERA_ENTITY,
camera_view(&self.camera),
self.camera.position,
CameraProjectionParams {
z_near: self.camera.z_near,
z_far: self.camera.z_far,
y_fov_rad: self.camera.y_fov_rad,
aspect,
orthographic: None,
},
EffectiveShading::from_settings(&self.host.settings, &Default::default()),
);
let view = ViewFrame {
camera,
viewport: (width.max(1), height.max(1)),
uptime_milliseconds: (self.time * 1000.0) as u64,
delta_time,
};
let mut inputs = compose_single_view(&mut self.host, view);
let mut outputs = Default::default();
if let Some(renderer) = self.renderer.as_mut()
&& let Err(error) = render_frame(renderer, &mut inputs, &mut outputs)
{
eprintln!("render_frame failed: {error}");
}
restore_single_view(&mut self.host, inputs);
}
fn render(&mut self) {
let Some(window) = self.window.as_ref() else {
return;
};
let size = window.inner_size();
if size.width == 0 || size.height == 0 {
return;
}
let now = std::time::Instant::now();
let delta_time = (now - self.last_frame).as_secs_f32().min(0.1);
self.last_frame = now;
self.frame(size.width, size.height, delta_time);
}
fn capture(&mut self, width: u32, height: u32, path: &std::path::Path) {
for _ in 0..self.shot_frames.max(1) {
self.frame(width, height, 1.0 / 60.0);
}
#[cfg(feature = "screenshot")]
{
if let Some(renderer) = self.renderer.as_mut() {
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
request_screenshot_copy(renderer, Some(path.to_path_buf()), None);
let mut guard = 0;
while renderer.screenshot_pending() && guard < 600 {
let _ = renderer
.device
.poll(nightshade_renderer::wgpu::PollType::wait_indefinitely());
poll_screenshot_readback(renderer);
guard += 1;
}
let mut written = 0;
let mut waited = 0;
while written == 0 && waited < 5000 {
written = std::fs::metadata(path).map(|meta| meta.len()).unwrap_or(0);
if written == 0 {
std::thread::sleep(std::time::Duration::from_millis(2));
waited += 1;
}
}
if written > 0 {
println!("wrote {}", path.display());
} else {
eprintln!("screenshot was not written");
}
}
}
#[cfg(not(feature = "screenshot"))]
{
let _ = path;
eprintln!("NS_SHOT needs the screenshot feature: --features wgpu,hdr,screenshot");
}
}
}
impl<E: Example> ApplicationHandler for Harness<E> {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.window.is_some() {
return;
}
let attributes = Window::default_attributes()
.with_title(self.title.clone())
.with_visible(self.shot.is_none())
.with_inner_size(winit::dpi::PhysicalSize::new(INITIAL_WIDTH, INITIAL_HEIGHT));
let window = Arc::new(
event_loop
.create_window(attributes)
.expect("failed to create window"),
);
let size = window.inner_size();
let mut renderer = pollster::block_on(WgpuRenderer::new_async(
window.clone(),
size.width.max(1),
size.height.max(1),
))
.expect("failed to create renderer");
install_presentation_passes(&mut renderer).expect("failed to install presentation passes");
self.renderer = Some(renderer);
self.state = Some(E::setup(&mut self.host, &mut self.camera));
if let Some(path) = self.shot.clone() {
self.capture(size.width.max(1), size.height.max(1), &path);
event_loop.exit();
return;
}
self.window = Some(window);
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::KeyboardInput {
event:
KeyEvent {
physical_key: PhysicalKey::Code(code),
state,
..
},
..
} => {
if code == KeyCode::Escape {
event_loop.exit();
} else if state == ElementState::Pressed {
self.keys.insert(code);
} else {
self.keys.remove(&code);
}
}
WindowEvent::Resized(size) if size.width > 0 && size.height > 0 => {
if let Some(renderer) = self.renderer.as_mut()
&& let Err(error) = renderer.resize_surface(size.width, size.height)
{
eprintln!("resize_surface failed: {error}");
}
}
WindowEvent::RedrawRequested => {
self.render();
if let Some(window) = self.window.as_ref() {
window.request_redraw();
}
}
_ => {}
}
}
}
pub fn run<E: Example>(title: &str)
where
E::State: 'static,
{
let shot = std::env::var("NS_SHOT").ok().map(std::path::PathBuf::from);
let shot_frames = std::env::var("NS_SHOT_FRAMES")
.ok()
.and_then(|value| value.parse().ok())
.unwrap_or(120);
let event_loop = EventLoop::new().expect("failed to create event loop");
event_loop.set_control_flow(ControlFlow::Poll);
let mut harness: Harness<E> = Harness {
title: title.to_string(),
window: None,
renderer: None,
host: new_host(),
camera: Camera::default(),
state: None,
keys: HashSet::new(),
last_frame: std::time::Instant::now(),
time: 0.0,
shot,
shot_frames,
};
event_loop.run_app(&mut harness).expect("event loop failed");
}