forked from nonsensical-dev/zenyx-engine
Merge branch 'main' of codeberg.org:Caznix/Zenyx
This commit is contained in:
commit
5a3a5d3e3d
7 changed files with 344 additions and 77 deletions
91
default.nix
91
default.nix
|
@ -1,5 +1,4 @@
|
||||||
{
|
{ lib,
|
||||||
lib,
|
|
||||||
rustPlatform,
|
rustPlatform,
|
||||||
nix-gitignore,
|
nix-gitignore,
|
||||||
bash,
|
bash,
|
||||||
|
@ -11,43 +10,63 @@
|
||||||
pkg-config,
|
pkg-config,
|
||||||
libxkbcommon,
|
libxkbcommon,
|
||||||
pkgs,
|
pkgs,
|
||||||
|
stdenv,
|
||||||
|
targetPackages ? pkgs,
|
||||||
}: let
|
}: let
|
||||||
version = (builtins.fromTOML (builtins.readFile ./engine/Cargo.toml)).package.version;
|
version = (builtins.fromTOML (builtins.readFile ./engine/Cargo.toml)).package.version;
|
||||||
src = nix-gitignore.gitignoreSource [] ./.;
|
src = nix-gitignore.gitignoreSource [] ./.;
|
||||||
in
|
in
|
||||||
rustPlatform.buildRustPackage rec {
|
rustPlatform.buildRustPackage rec {
|
||||||
pname = "zenyx";
|
pname = "zenyx";
|
||||||
inherit src version;
|
inherit src version;
|
||||||
cargoLock.lockFile = ./Cargo.lock;
|
cargoLock.lockFile = ./Cargo.lock;
|
||||||
nativeBuildInputs = [
|
|
||||||
makeWrapper
|
|
||||||
pkg-config
|
|
||||||
];
|
|
||||||
buildInputs = with pkgs; [
|
|
||||||
wayland
|
|
||||||
vulkan-loader
|
|
||||||
libxkbcommon
|
|
||||||
libGL
|
|
||||||
libxkbcommon
|
|
||||||
xorg.libXcursor
|
|
||||||
xorg.libXrandr
|
|
||||||
xorg.libXi
|
|
||||||
xorg.libX11
|
|
||||||
xorg.libxcb
|
|
||||||
bash
|
|
||||||
dav1d
|
|
||||||
];
|
|
||||||
doCheck = false;
|
|
||||||
LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath buildInputs}";
|
|
||||||
|
|
||||||
fixupPhase = ''
|
nativeBuildInputs = [
|
||||||
wrapProgram $out/bin/${pname} --set PATH ${bash}/bin:\$PATH --set LD_LIBRARY_PATH ${pkgs.lib.makeLibraryPath buildInputs}
|
pkg-config
|
||||||
'';
|
] ++ lib.optionals stdenv.targetPlatform.isDarwin [
|
||||||
|
targetPackages.darwin.apple_sdk.frameworks.CoreServices
|
||||||
|
];
|
||||||
|
|
||||||
meta = {
|
buildInputs = with targetPackages; [
|
||||||
description = "Test";
|
dav1d
|
||||||
license = lib.licenses.mit;
|
] ++ lib.optionals (stdenv.targetPlatform.isLinux || stdenv.targetPlatform.isWindows) [
|
||||||
platforms = lib.platforms.linux;
|
vulkan-loader
|
||||||
mainProgram = "zenyx";
|
] ++ lib.optionals stdenv.targetPlatform.isLinux [
|
||||||
};
|
makeWrapper
|
||||||
}
|
wayland
|
||||||
|
libxkbcommon
|
||||||
|
libGL
|
||||||
|
xorg.libXcursor
|
||||||
|
xorg.libXrandr
|
||||||
|
xorg.libXi
|
||||||
|
xorg.libX11
|
||||||
|
xorg.libxcb
|
||||||
|
bash
|
||||||
|
] ++ lib.optionals stdenv.targetPlatform.isDarwin [
|
||||||
|
makeWrapper
|
||||||
|
darwin.apple_sdk.frameworks.Cocoa
|
||||||
|
darwin.apple_sdk.frameworks.Metal
|
||||||
|
darwin.apple_sdk.frameworks.CoreVideo
|
||||||
|
darwin.apple_sdk.frameworks.QuartzCore
|
||||||
|
];
|
||||||
|
|
||||||
|
CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER =
|
||||||
|
lib.optionalString stdenv.targetPlatform.isWindows "${stdenv.cc.targetPrefix}gcc";
|
||||||
|
|
||||||
|
NIX_LDFLAGS = lib.optionalString stdenv.targetPlatform.isDarwin "-framework CoreFoundation";
|
||||||
|
|
||||||
|
postInstall = lib.optionalString stdenv.targetPlatform.isLinux ''
|
||||||
|
wrapProgram $out/bin/${pname} \
|
||||||
|
--prefix PATH : ${lib.makeBinPath [ bash ]} \
|
||||||
|
--set LD_LIBRARY_PATH ${lib.makeLibraryPath buildInputs}
|
||||||
|
'';
|
||||||
|
|
||||||
|
doCheck = false;
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Cross-platform WSYWIG Game Engine";
|
||||||
|
license = lib.licenses.mit;
|
||||||
|
platforms = lib.platforms.all;
|
||||||
|
mainProgram = "zenyx";
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
use std::any::{Any, TypeId};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
pub trait Component: Sized + 'static {
|
||||||
|
fn update(&mut self, delta_time: f32);
|
||||||
|
fn serialize(&self) -> Vec<u8>;
|
||||||
|
fn deserialize(data: &[u8; 6]) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Entity: Sized {
|
||||||
|
fn add_component<C: Component>(&mut self, component: C);
|
||||||
|
fn remove_component<C: Component>(&mut self);
|
||||||
|
fn get_component<C: Component>(&self) -> Option<&C>;
|
||||||
|
fn serialize(&self) -> Vec<u8>;
|
||||||
|
fn deserialize(data: &[u8; 6]) -> Self;
|
||||||
|
}
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
// Global registry mapping component TypeId to a unique bit flag.
|
||||||
|
static ref COMPONENT_REGISTRY: Mutex<HashMap<TypeId, u64>> = Mutex::new(HashMap::new());
|
||||||
|
static ref NEXT_COMPONENT_BIT: Mutex<u64> = Mutex::new(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// To allow dynamic dispatch on components (even though Component itself is not object‐safe)
|
||||||
|
// we wrap them in an object–safe trait.
|
||||||
|
pub trait ComponentObject: Any {
|
||||||
|
fn update_obj(&mut self, delta_time: f32);
|
||||||
|
fn serialize_obj(&self) -> Vec<u8>;
|
||||||
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
}
|
||||||
|
impl<T: Component + 'static> ComponentObject for T {
|
||||||
|
fn update_obj(&mut self, delta_time: f32) {
|
||||||
|
T::update(self, delta_time)
|
||||||
|
}
|
||||||
|
fn serialize_obj(&self) -> Vec<u8> {
|
||||||
|
T::serialize(self)
|
||||||
|
}
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EntityImpl {
|
||||||
|
id: usize,
|
||||||
|
bitmask: u64,
|
||||||
|
// The key is the unique bit flag for the component type.
|
||||||
|
components: HashMap<u64, Box<dyn ComponentObject>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EntityImpl {
|
||||||
|
pub fn new(id: usize) -> Self {
|
||||||
|
EntityImpl {
|
||||||
|
id,
|
||||||
|
bitmask: 0,
|
||||||
|
components: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entity for EntityImpl {
|
||||||
|
fn add_component<C: Component>(&mut self, component: C) {
|
||||||
|
let type_id = TypeId::of::<C>();
|
||||||
|
let mut registry = COMPONENT_REGISTRY.lock().unwrap();
|
||||||
|
let bit = registry.entry(type_id).or_insert_with(|| {
|
||||||
|
let mut next_bit = NEXT_COMPONENT_BIT.lock().unwrap();
|
||||||
|
let current = *next_bit;
|
||||||
|
*next_bit *= 2;
|
||||||
|
current
|
||||||
|
});
|
||||||
|
self.bitmask |= *bit;
|
||||||
|
self.components.insert(*bit, Box::new(component));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_component<C: Component>(&mut self) {
|
||||||
|
let type_id = TypeId::of::<C>();
|
||||||
|
if let Some(&bit) = COMPONENT_REGISTRY.lock().unwrap().get(&type_id) {
|
||||||
|
self.bitmask &= !bit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_component<C: Component>(&self) -> Option<&C> {
|
||||||
|
let type_id = TypeId::of::<C>();
|
||||||
|
if let Some(&bit) = COMPONENT_REGISTRY.lock().unwrap().get(&type_id) {
|
||||||
|
self.components
|
||||||
|
.get(&bit)
|
||||||
|
.and_then(|boxed| boxed.as_any().downcast_ref::<C>())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serialize(&self) -> Vec<u8> {
|
||||||
|
// Serialize the entity's bitmask into 6 bytes (lowest 48 bits).
|
||||||
|
let mut bytes = self.bitmask.to_le_bytes().to_vec();
|
||||||
|
bytes.truncate(6);
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize(data: &[u8; 6]) -> Self {
|
||||||
|
let mut full = [0u8; 8];
|
||||||
|
full[..6].copy_from_slice(data);
|
||||||
|
let bitmask = u64::from_le_bytes(full);
|
||||||
|
// When deserializing, we recreate an entity with the restored bitmask.
|
||||||
|
// Note: The individual component data are not restored here.
|
||||||
|
Self {
|
||||||
|
id: 0,
|
||||||
|
bitmask,
|
||||||
|
components: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ECS {
|
||||||
|
next_entity_id: usize,
|
||||||
|
pub entities: HashMap<usize, EntityImpl>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ECS {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
ECS {
|
||||||
|
next_entity_id: 0,
|
||||||
|
entities: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_entity(&mut self) -> &mut EntityImpl {
|
||||||
|
let entity = EntityImpl::new(self.next_entity_id);
|
||||||
|
self.entities.insert(self.next_entity_id, entity);
|
||||||
|
self.next_entity_id += 1;
|
||||||
|
self.entities.get_mut(&(self.next_entity_id - 1)).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, delta_time: f32) {
|
||||||
|
for entity in self.entities.values_mut() {
|
||||||
|
// Update each component attached to the entity.
|
||||||
|
for comp in entity.components.values_mut() {
|
||||||
|
comp.update_obj(delta_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
// For each entity, store its id (8 bytes) and its 6-byte bitmask.
|
||||||
|
for (id, entity) in &self.entities {
|
||||||
|
data.extend_from_slice(&id.to_le_bytes());
|
||||||
|
data.extend_from_slice(&entity.serialize());
|
||||||
|
}
|
||||||
|
data
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize(&mut self, data: &[u8]) {
|
||||||
|
self.entities.clear();
|
||||||
|
// Each serialized entity uses 8 (id) + 6 (bitmask) = 14 bytes.
|
||||||
|
let entity_size = 14;
|
||||||
|
let count = data.len() / entity_size;
|
||||||
|
for i in 0..count {
|
||||||
|
let offset = i * entity_size;
|
||||||
|
let mut id_bytes = [0u8; 8];
|
||||||
|
id_bytes.copy_from_slice(&data[offset..offset + 8]);
|
||||||
|
let id = usize::from_le_bytes(id_bytes);
|
||||||
|
|
||||||
|
let mut mask_bytes = [0u8; 6];
|
||||||
|
mask_bytes.copy_from_slice(&data[offset + 8..offset + 14]);
|
||||||
|
let entity = EntityImpl::deserialize(&mask_bytes);
|
||||||
|
self.entities.insert(id, entity);
|
||||||
|
}
|
||||||
|
self.next_entity_id = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -49,33 +49,39 @@ fn process_panic(info: &std::panic::PanicHookInfo<'_>) -> Result<(), Box<dyn Err
|
||||||
writeln!(file, "{}", render_backtrace().sanitize_path())?;
|
writeln!(file, "{}", render_backtrace().sanitize_path())?;
|
||||||
|
|
||||||
let panic_msg = format!(
|
let panic_msg = format!(
|
||||||
"Zenyx had a problem and crashed. To help us diagnose the problem you can send us a crash report.
|
r#"Zenyx had a problem and crashed. To help us diagnose the problem you can send us a crash report.
|
||||||
|
|
||||||
We have generated a report file at '{}'. Submit an issue or email with the subject of 'Zenyx Crash Report' and include the report as an attachment.
|
We have generated a report file at '{}'. Submit an issue or email with the subject of 'Zenyx Crash Report' and include the report as an attachment.
|
||||||
|
|
||||||
To submit the crash report:
|
To submit the crash report:
|
||||||
|
|
||||||
https://github.com/Zenyx-Engine/Zenyx/issues
|
https://github.com/Zenyx-Engine/Zenyx/issues
|
||||||
|
|
||||||
We take privacy seriously, and do not perform any automated error collection. In order to improve the software, we rely on people to submit reports.
|
We take privacy seriously, and do not perform any automated error collection. In order to improve the software, we rely on people to submit reports.
|
||||||
|
|
||||||
Thank you kindly!", log_path.display());
|
Thank you kindly!"#,
|
||||||
println!("{}", panic_msg.red().bold());
|
log_path.display()
|
||||||
println!(
|
|
||||||
"\nFor future reference, the error summary is as follows:\n{}",
|
|
||||||
payload_str.red().bold()
|
|
||||||
);
|
);
|
||||||
if let Err(e ) = MessageDialog::new()
|
|
||||||
.set_type(MessageType::Error)
|
|
||||||
.set_title(&format!("{}",payload_str))
|
|
||||||
// wont display properly if not debug formatted??
|
|
||||||
.set_text(&format!("{:#?}", panic_msg))
|
|
||||||
.show_alert() {
|
|
||||||
error!("{e}");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let final_msg = format!(
|
||||||
|
r#"{}
|
||||||
|
|
||||||
|
For future reference, the error summary is as follows:
|
||||||
|
{}"#,
|
||||||
|
panic_msg, payload_str
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("{}", final_msg.red().bold());
|
||||||
|
|
||||||
|
MessageDialog::new()
|
||||||
|
.set_type(MessageType::Error)
|
||||||
|
.set_title("A fatal error in Zenyx has occurred")
|
||||||
|
.set_text(&final_msg)
|
||||||
|
.show_alert()
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Failed to show error dialog: {}", e);
|
||||||
|
e.into()
|
||||||
|
})
|
||||||
|
}
|
||||||
fn render_backtrace() -> String {
|
fn render_backtrace() -> String {
|
||||||
const HEX_WIDTH: usize = mem::size_of::<usize>() * 2 + 2;
|
const HEX_WIDTH: usize = mem::size_of::<usize>() * 2 + 2;
|
||||||
const NEXT_SYMBOL_PADDING: usize = HEX_WIDTH + 6;
|
const NEXT_SYMBOL_PADDING: usize = HEX_WIDTH + 6;
|
||||||
|
|
|
@ -6,6 +6,8 @@ use std::time::Instant;
|
||||||
use cgmath::{Deg, Matrix4, Point3, Rad, SquareMatrix, Vector3, perspective};
|
use cgmath::{Deg, Matrix4, Point3, Rad, SquareMatrix, Vector3, perspective};
|
||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
use tracing::{debug, error, info, trace};
|
use tracing::{debug, error, info, trace};
|
||||||
|
use thiserror::Error;
|
||||||
|
use tracing::{debug, error, info, trace};
|
||||||
use wgpu::TextureUsages;
|
use wgpu::TextureUsages;
|
||||||
use wgpu::{Backends, InstanceDescriptor, util::DeviceExt};
|
use wgpu::{Backends, InstanceDescriptor, util::DeviceExt};
|
||||||
use wgpu_text::glyph_brush::ab_glyph::FontRef;
|
use wgpu_text::glyph_brush::ab_glyph::FontRef;
|
||||||
|
@ -223,7 +225,7 @@ struct FontState {
|
||||||
impl<'window> Renderer<'window> {
|
impl<'window> Renderer<'window> {
|
||||||
pub async fn new(window: Arc<Window>) -> Result<Self> {
|
pub async fn new(window: Arc<Window>) -> Result<Self> {
|
||||||
let instance = wgpu::Instance::new(&InstanceDescriptor {
|
let instance = wgpu::Instance::new(&InstanceDescriptor {
|
||||||
backends: Backends::from_comma_list("dx12,metal,opengl,webgpu"),
|
backends: Backends::from_comma_list("dx12,metal,opengl,webgpu,vulkan"),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -344,11 +346,23 @@ impl<'window> Renderer<'window> {
|
||||||
let camera = Camera::new(&device, &camera_bind_group_layout, width, height);
|
let camera = Camera::new(&device, &camera_bind_group_layout, width, height);
|
||||||
|
|
||||||
let surface_caps = surface.get_capabilities(&adapter);
|
let surface_caps = surface.get_capabilities(&adapter);
|
||||||
|
let present_mode = [
|
||||||
|
wgpu::PresentMode::Immediate,
|
||||||
|
wgpu::PresentMode::Mailbox,
|
||||||
|
wgpu::PresentMode::AutoNoVsync,
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.find(|mode| surface_caps.present_modes.contains(mode))
|
||||||
|
.unwrap_or(wgpu::PresentMode::Fifo);
|
||||||
|
|
||||||
|
debug!("Using {:#?} present mode.", present_mode);
|
||||||
|
|
||||||
let surface_config = wgpu::SurfaceConfiguration {
|
let surface_config = wgpu::SurfaceConfiguration {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
format: surface_caps.formats[0],
|
format: surface_caps.formats[0],
|
||||||
present_mode: wgpu::PresentMode::AutoNoVsync,
|
present_mode,
|
||||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||||
view_formats: vec![],
|
view_formats: vec![],
|
||||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||||
|
@ -360,6 +374,8 @@ impl<'window> Renderer<'window> {
|
||||||
surface_config.width,
|
surface_config.width,
|
||||||
surface_config.height,
|
surface_config.height,
|
||||||
);
|
);
|
||||||
|
let (depth_texture, depth_texture_view) =
|
||||||
|
create_depth_texture(&device, surface_config.width, surface_config.height);
|
||||||
|
|
||||||
let font_bytes = include_bytes!("DejaVuSans.ttf");
|
let font_bytes = include_bytes!("DejaVuSans.ttf");
|
||||||
let font = FontRef::try_from_slice(font_bytes).map_err(|e| {
|
let font = FontRef::try_from_slice(font_bytes).map_err(|e| {
|
||||||
|
@ -605,7 +621,6 @@ fn create_depth_texture(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
// format: wgpu::TextureFormat,
|
|
||||||
) -> (wgpu::Texture, wgpu::TextureView) {
|
) -> (wgpu::Texture, wgpu::TextureView) {
|
||||||
let size = wgpu::Extent3d {
|
let size = wgpu::Extent3d {
|
||||||
width,
|
width,
|
||||||
|
|
|
@ -7,12 +7,15 @@ use winit::dpi::Size;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use tobj::Mesh;
|
||||||
use tobj::{LoadOptions, Model};
|
use tobj::{LoadOptions, Model};
|
||||||
use tracing::{debug, error, info, trace, warn};
|
use tracing::{debug, error, info, trace, warn};
|
||||||
use wgpu::rwh::HasWindowHandle;
|
use wgpu::rwh::HasWindowHandle;
|
||||||
use winit::application::ApplicationHandler;
|
use winit::application::ApplicationHandler;
|
||||||
use winit::event::{KeyEvent, WindowEvent};
|
use winit::event::{KeyEvent, WindowEvent};
|
||||||
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
|
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
|
||||||
|
use winit::monitor::MonitorHandle;
|
||||||
|
use winit::window::Fullscreen;
|
||||||
use winit::window::Window;
|
use winit::window::Window;
|
||||||
use winit::window::WindowId;
|
use winit::window::WindowId;
|
||||||
|
|
||||||
|
@ -44,16 +47,16 @@ pub struct App<'window> {
|
||||||
static CUBE_OBJ: &str = "
|
static CUBE_OBJ: &str = "
|
||||||
# Blender 4.2.3 LTS
|
# Blender 4.2.3 LTS
|
||||||
# www.blender.org
|
# www.blender.org
|
||||||
mtllib cube.mtl
|
mtllib untitled.mtl
|
||||||
o Cube
|
o Cube
|
||||||
v 1.000000 1.000000 -1.000000
|
v 0.645975 0.645975 -0.645975
|
||||||
v 1.000000 -1.000000 -1.000000
|
v 0.645975 -0.645975 -0.645975
|
||||||
v 1.000000 1.000000 1.000000
|
v 0.645975 0.645975 0.645975
|
||||||
v 1.000000 -1.000000 1.000000
|
v 0.645975 -0.645975 0.645975
|
||||||
v -1.000000 1.000000 -1.000000
|
v -0.645975 0.645975 -0.645975
|
||||||
v -1.000000 -1.000000 -1.000000
|
v -0.645975 -0.645975 -0.645975
|
||||||
v -1.000000 1.000000 1.000000
|
v -0.645975 0.645975 0.645975
|
||||||
v -1.000000 -1.000000 1.000000
|
v -0.645975 -0.645975 0.645975
|
||||||
vn -0.0000 1.0000 -0.0000
|
vn -0.0000 1.0000 -0.0000
|
||||||
vn -0.0000 -0.0000 1.0000
|
vn -0.0000 -0.0000 1.0000
|
||||||
vn -1.0000 -0.0000 -0.0000
|
vn -1.0000 -0.0000 -0.0000
|
||||||
|
@ -82,6 +85,7 @@ f 8/8/3 7/9/3 5/10/3 6/11/3
|
||||||
f 6/12/4 2/13/4 4/5/4 8/14/4
|
f 6/12/4 2/13/4 4/5/4 8/14/4
|
||||||
f 2/13/5 1/1/5 3/4/5 4/5/5
|
f 2/13/5 1/1/5 3/4/5 4/5/5
|
||||||
f 6/11/6 5/10/6 1/1/6 2/13/6
|
f 6/11/6 5/10/6 1/1/6 2/13/6
|
||||||
|
|
||||||
";
|
";
|
||||||
|
|
||||||
impl App<'_> {
|
impl App<'_> {
|
||||||
|
@ -103,10 +107,22 @@ impl App<'_> {
|
||||||
) {
|
) {
|
||||||
Ok(obj) => obj,
|
Ok(obj) => obj,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("{e}");
|
error!("Failed to load Pumpkin.obj: {e}");
|
||||||
panic!()
|
// Fallback to CUBE_OBJ
|
||||||
|
let fallback_obj = CUBE_OBJ.to_string();
|
||||||
|
tobj::load_obj_buf(
|
||||||
|
&mut fallback_obj.as_bytes(),
|
||||||
|
&LoadOptions {
|
||||||
|
triangulate: true,
|
||||||
|
single_index: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
|_| Ok(Default::default()),
|
||||||
|
)
|
||||||
|
.expect("Failed to load fallback CUBE_OBJ")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (combined_vertices, combined_indices) = parse_obj(&obj.0);
|
let (combined_vertices, combined_indices) = parse_obj(&obj.0);
|
||||||
|
|
||||||
wgpu_ctx.add_model(&combined_vertices, &combined_indices);
|
wgpu_ctx.add_model(&combined_vertices, &combined_indices);
|
||||||
|
@ -121,10 +137,10 @@ impl App<'_> {
|
||||||
);
|
);
|
||||||
info!("Main window created: {:?}", window_id);
|
info!("Main window created: {:?}", window_id);
|
||||||
}
|
}
|
||||||
Err(e) => error!("Failed to create WGPU context: {:#}", e),
|
Err(e) => error!("Failed to create WGPU context: {:}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => error!("Failed to create main window: {:#}", e),
|
Err(e) => error!("Failed to create main window: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,6 +169,7 @@ impl App<'_> {
|
||||||
winit::keyboard::KeyCode::Escape => {
|
winit::keyboard::KeyCode::Escape => {
|
||||||
self.spawn_child_window(event_loop);
|
self.spawn_child_window(event_loop);
|
||||||
}
|
}
|
||||||
|
winit::keyboard::KeyCode::F11 => self.toggle_fullscreen(window_id),
|
||||||
other => error!("Unimplemented keycode: {:?}", other),
|
other => error!("Unimplemented keycode: {:?}", other),
|
||||||
},
|
},
|
||||||
_ => debug!("Received a keyboard event with no physical key"),
|
_ => debug!("Received a keyboard event with no physical key"),
|
||||||
|
@ -182,6 +199,23 @@ impl App<'_> {
|
||||||
warn!("No window context for toggling background: {:?}", window_id);
|
warn!("No window context for toggling background: {:?}", window_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn toggle_fullscreen(&mut self, window_id: WindowId) {
|
||||||
|
if let Some(ctx) = self.windows.get_mut(&window_id) {
|
||||||
|
let is_fullscreen = ctx.window.fullscreen().is_some();
|
||||||
|
let fullscreen_mode = if is_fullscreen {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
ctx.window
|
||||||
|
.current_monitor()
|
||||||
|
.map(|monitor| Fullscreen::Borderless(Some(monitor)))
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.window.set_fullscreen(fullscreen_mode);
|
||||||
|
debug!("Fullscreen toggled for window: {:?}", window_id);
|
||||||
|
} else {
|
||||||
|
warn!("No window found for fullscreen toggle: {:?}", window_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn_child_window(&mut self, event_loop: &ActiveEventLoop) {
|
fn spawn_child_window(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
if let Some(main_ctx) = self.windows.values().find(|ctx| ctx.is_main_window()) {
|
if let Some(main_ctx) = self.windows.values().find(|ctx| ctx.is_main_window()) {
|
||||||
|
@ -213,7 +247,7 @@ impl App<'_> {
|
||||||
let mut tmp_path: PathBuf = env::temp_dir();
|
let mut tmp_path: PathBuf = env::temp_dir();
|
||||||
tmp_path.push("cube.obj");
|
tmp_path.push("cube.obj");
|
||||||
if let Err(e) = fs::write(&tmp_path, CUBE_OBJ) {
|
if let Err(e) = fs::write(&tmp_path, CUBE_OBJ) {
|
||||||
error!("Failed to write cube OBJ to temp: {:?}", e);
|
error!("Failed to write cube OBJ to temp: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
let load_options = tobj::LoadOptions {
|
let load_options = tobj::LoadOptions {
|
||||||
|
@ -228,7 +262,7 @@ impl App<'_> {
|
||||||
wgpu_ctx.add_model(&cube_vertices, &cube_indices);
|
wgpu_ctx.add_model(&cube_vertices, &cube_indices);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to load cube OBJ from temp file: {:?}", e)
|
error!("Failed to load cube OBJ from temp file: {:#}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,10 +276,10 @@ impl App<'_> {
|
||||||
);
|
);
|
||||||
debug!("Spawned new child window: {:?}", window_id);
|
debug!("Spawned new child window: {:?}", window_id);
|
||||||
}
|
}
|
||||||
Err(e) => error!("Failed to create WGPU context for child window: {:?}", e),
|
Err(e) => error!("Failed to create WGPU context for child window: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => error!("Failed to create child window: {:?}", e),
|
Err(e) => error!("Failed to create child window: {}", e),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
error!("No main window found. Cannot spawn a child window.");
|
error!("No main window found. Cannot spawn a child window.");
|
||||||
|
|
|
@ -46,13 +46,29 @@ async fn main() {
|
||||||
|
|
||||||
info!("Type 'help' for a list of commands.");
|
info!("Type 'help' for a list of commands.");
|
||||||
let repl_thread = std::thread::spawn(|| {
|
let repl_thread = std::thread::spawn(|| {
|
||||||
let rt = runtime::Builder::new_current_thread()
|
let rt = match runtime::Builder::new_current_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build() {
|
||||||
.unwrap();
|
Ok(rt) => rt,
|
||||||
|
Err(e) => {
|
||||||
|
error!("A fatal error has occured: {e}");
|
||||||
|
std::process::exit(1)
|
||||||
|
},
|
||||||
|
};
|
||||||
rt.block_on(core::repl::input::handle_repl())
|
rt.block_on(core::repl::input::handle_repl())
|
||||||
});
|
});
|
||||||
core::render::init_renderer(event_loop);
|
splash::print_splash();
|
||||||
|
info!("Type 'help' for a list of commands.");
|
||||||
|
|
||||||
|
match EventLoop::new() {
|
||||||
|
Ok(event_loop) => {
|
||||||
|
core::render::init_renderer(event_loop);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("{e}")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if let Err(_) = repl_thread.join() {
|
if let Err(_) = repl_thread.join() {
|
||||||
error!("REPL thread panicked");
|
error!("REPL thread panicked");
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,12 @@
|
||||||
packages = {
|
packages = {
|
||||||
inherit (pkgs) zenyx;
|
inherit (pkgs) zenyx;
|
||||||
default = pkgs.zenyx;
|
default = pkgs.zenyx;
|
||||||
|
windows = let
|
||||||
|
pkgsCross = import nixpkgs {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
crossSystem = nixpkgs.lib.systems.examples.mingwW64;
|
||||||
|
};
|
||||||
|
in pkgsCross.callPackage ./default.nix {};
|
||||||
};
|
};
|
||||||
|
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue