MAJOR rendering overhaul. Snow deformation, persistent light, flowmap out. Also ECS architexture overhaul
This commit is contained in:
98
src/loaders/empty.rs
Normal file
98
src/loaders/empty.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use glam::Mat4;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct EmptyNode
|
||||
{
|
||||
pub name: String,
|
||||
pub transform: Mat4,
|
||||
}
|
||||
|
||||
impl EmptyNode
|
||||
{
|
||||
pub fn new(name: String, transform: Mat4) -> Self
|
||||
{
|
||||
Self { name, transform }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Empties
|
||||
{
|
||||
nodes: Vec<EmptyNode>,
|
||||
}
|
||||
|
||||
impl Empties
|
||||
{
|
||||
fn new(nodes: Vec<EmptyNode>) -> Self
|
||||
{
|
||||
Self { nodes }
|
||||
}
|
||||
|
||||
pub fn into_nodes(self) -> Vec<EmptyNode>
|
||||
{
|
||||
self.nodes
|
||||
}
|
||||
|
||||
pub fn load_gltf_empties(path: impl AsRef<Path>)
|
||||
-> Result<Empties, Box<dyn std::error::Error>>
|
||||
{
|
||||
let (gltf, _buffers, _images) = gltf::import(path)?;
|
||||
|
||||
let mut all_empties = Vec::new();
|
||||
|
||||
for scene in gltf.scenes()
|
||||
{
|
||||
for node in scene.nodes()
|
||||
{
|
||||
Self::process_node(&node, Mat4::IDENTITY, &mut all_empties)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Empties::new(all_empties))
|
||||
}
|
||||
|
||||
fn process_node(
|
||||
node: &gltf::Node,
|
||||
parent_transform: Mat4,
|
||||
all_empties: &mut Vec<EmptyNode>,
|
||||
) -> Result<(), Box<dyn std::error::Error>>
|
||||
{
|
||||
let local_transform = Mat4::from_cols_array_2d(&node.transform().matrix());
|
||||
let global_transform = parent_transform * local_transform;
|
||||
|
||||
let is_empty = node.mesh().is_none() && node.light().is_none() && node.camera().is_none();
|
||||
|
||||
if is_empty
|
||||
{
|
||||
let name = node.name().unwrap_or("Unnamed").to_string();
|
||||
all_empties.push(EmptyNode::new(name, global_transform));
|
||||
}
|
||||
|
||||
for child in node.children()
|
||||
{
|
||||
Self::process_node(&child, global_transform, all_empties)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_empties(path: impl AsRef<Path>) -> Result<Empties, Box<dyn std::error::Error>>
|
||||
{
|
||||
Self::load_gltf_empties(path)
|
||||
}
|
||||
|
||||
pub fn get_empty_by_name(gltf_path: &str, name: &str) -> anyhow::Result<Option<EmptyNode>>
|
||||
{
|
||||
let empties = Self::load_empties(gltf_path)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to load empty nodes: {}", e))?;
|
||||
|
||||
for empty_node in empties.into_nodes()
|
||||
{
|
||||
if empty_node.name == name
|
||||
{
|
||||
return Ok(Some(empty_node));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
67
src/loaders/heightmap.rs
Normal file
67
src/loaders/heightmap.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use exr::prelude::{ReadChannels, ReadLayers};
|
||||
use std::path::Path;
|
||||
|
||||
pub fn load_exr_heightmap(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
path: impl AsRef<Path>,
|
||||
) -> Result<(wgpu::Texture, wgpu::TextureView, wgpu::Sampler), Box<dyn std::error::Error>>
|
||||
{
|
||||
let image = exr::prelude::read()
|
||||
.no_deep_data()
|
||||
.largest_resolution_level()
|
||||
.all_channels()
|
||||
.all_layers()
|
||||
.all_attributes()
|
||||
.from_file(path)?;
|
||||
|
||||
let layer = &image.layer_data[0];
|
||||
let width = layer.size.width() as u32;
|
||||
let height = layer.size.height() as u32;
|
||||
|
||||
let channel = &layer.channel_data.list[0];
|
||||
let float_data: Vec<f32> = channel.sample_data.values_as_f32().collect();
|
||||
|
||||
let texture_size = wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("Height Map Texture"),
|
||||
size: texture_size,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::R32Float,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
queue.write_texture(
|
||||
texture.as_image_copy(),
|
||||
bytemuck::cast_slice(&float_data),
|
||||
wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(4 * width),
|
||||
rows_per_image: Some(height),
|
||||
},
|
||||
texture_size,
|
||||
);
|
||||
|
||||
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("Height Map Sampler"),
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
Ok((texture, view, sampler))
|
||||
}
|
||||
125
src/loaders/lights.rs
Normal file
125
src/loaders/lights.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use glam::{Mat4, Vec3};
|
||||
use std::path::Path;
|
||||
|
||||
use crate::components::lights::spot::SpotlightComponent;
|
||||
|
||||
pub struct LightData
|
||||
{
|
||||
pub component: SpotlightComponent,
|
||||
pub transform: Mat4,
|
||||
pub tag: Option<String>,
|
||||
}
|
||||
|
||||
pub struct Lights
|
||||
{
|
||||
spotlights: Vec<LightData>,
|
||||
}
|
||||
|
||||
impl Lights
|
||||
{
|
||||
fn new(spotlights: Vec<LightData>) -> Self
|
||||
{
|
||||
Self { spotlights }
|
||||
}
|
||||
|
||||
pub fn into_spotlights(self) -> Vec<LightData>
|
||||
{
|
||||
self.spotlights
|
||||
}
|
||||
|
||||
pub fn load_gltf_lights(path: impl AsRef<Path>) -> Result<Lights, Box<dyn std::error::Error>>
|
||||
{
|
||||
let (gltf, _buffers, _images) = gltf::import(path)?;
|
||||
|
||||
let mut all_directional = Vec::new();
|
||||
let mut all_point = Vec::new();
|
||||
let mut all_spot = Vec::new();
|
||||
|
||||
for scene in gltf.scenes()
|
||||
{
|
||||
for node in scene.nodes()
|
||||
{
|
||||
Self::process_node(
|
||||
&node,
|
||||
Mat4::IDENTITY,
|
||||
&mut all_directional,
|
||||
&mut all_point,
|
||||
&mut all_spot,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Lights::new(all_spot))
|
||||
}
|
||||
|
||||
fn process_node(
|
||||
node: &gltf::Node,
|
||||
parent_transform: Mat4,
|
||||
all_directional: &mut Vec<LightData>,
|
||||
all_point: &mut Vec<LightData>,
|
||||
all_spot: &mut Vec<LightData>,
|
||||
) -> Result<(), Box<dyn std::error::Error>>
|
||||
{
|
||||
let local_transform = Mat4::from_cols_array_2d(&node.transform().matrix());
|
||||
let global_transform = parent_transform * local_transform;
|
||||
|
||||
if let Some(light) = node.light()
|
||||
{
|
||||
let local_transform = Mat4::from_cols_array_2d(&node.transform().matrix());
|
||||
let global_transform = parent_transform * local_transform;
|
||||
let (_scale, rotation, _translation) = global_transform.to_scale_rotation_translation();
|
||||
|
||||
let tag = serde_json::to_value(light.extras())
|
||||
.ok()
|
||||
.and_then(|extras| {
|
||||
extras
|
||||
.get("light_tag")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(String::from)
|
||||
});
|
||||
|
||||
match light.kind()
|
||||
{
|
||||
gltf::khr_lights_punctual::Kind::Directional => todo!(),
|
||||
gltf::khr_lights_punctual::Kind::Point => todo!(),
|
||||
gltf::khr_lights_punctual::Kind::Spot {
|
||||
inner_cone_angle,
|
||||
outer_cone_angle,
|
||||
} =>
|
||||
{
|
||||
let range = light.range().unwrap_or(100.0);
|
||||
let spotlight = SpotlightComponent::new(
|
||||
Vec3::ZERO,
|
||||
rotation * -Vec3::Z,
|
||||
range,
|
||||
inner_cone_angle,
|
||||
outer_cone_angle,
|
||||
);
|
||||
all_spot.push(LightData {
|
||||
component: spotlight,
|
||||
transform: global_transform,
|
||||
tag,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for child in node.children()
|
||||
{
|
||||
Self::process_node(
|
||||
&child,
|
||||
global_transform,
|
||||
all_directional,
|
||||
all_point,
|
||||
all_spot,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_lights(path: impl AsRef<Path>) -> Result<Lights, Box<dyn std::error::Error>>
|
||||
{
|
||||
crate::render::with_device(|_device| Lights::load_gltf_lights(path))
|
||||
}
|
||||
}
|
||||
755
src/loaders/mesh.rs
Normal file
755
src/loaders/mesh.rs
Normal file
@@ -0,0 +1,755 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use glam::{Mat4, Quat, Vec3};
|
||||
use std::path::Path;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
pub struct Vertex
|
||||
{
|
||||
pub position: [f32; 3],
|
||||
pub normal: [f32; 3],
|
||||
pub uv: [f32; 2],
|
||||
}
|
||||
|
||||
impl Vertex
|
||||
{
|
||||
pub fn desc() -> wgpu::VertexBufferLayout<'static>
|
||||
{
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &[
|
||||
wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
shader_location: 0,
|
||||
format: wgpu::VertexFormat::Float32x3,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
||||
shader_location: 1,
|
||||
format: wgpu::VertexFormat::Float32x3,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: (std::mem::size_of::<[f32; 3]>() * 2) as wgpu::BufferAddress,
|
||||
shader_location: 2,
|
||||
format: wgpu::VertexFormat::Float32x2,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InstanceData
|
||||
{
|
||||
pub position: Vec3,
|
||||
pub rotation: Quat,
|
||||
pub scale: Vec3,
|
||||
pub dissolve_amount: f32,
|
||||
}
|
||||
|
||||
impl InstanceData
|
||||
{
|
||||
pub fn to_raw(&self) -> InstanceRaw
|
||||
{
|
||||
let model = Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.position);
|
||||
InstanceRaw {
|
||||
model: model.to_cols_array_2d(),
|
||||
dissolve_amount: self.dissolve_amount,
|
||||
_padding: [0.0; 3],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
pub struct InstanceRaw
|
||||
{
|
||||
pub model: [[f32; 4]; 4],
|
||||
pub dissolve_amount: f32,
|
||||
pub _padding: [f32; 3],
|
||||
}
|
||||
|
||||
impl InstanceRaw
|
||||
{
|
||||
pub fn desc() -> wgpu::VertexBufferLayout<'static>
|
||||
{
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<InstanceRaw>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &[
|
||||
wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
shader_location: 3,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: std::mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
|
||||
shader_location: 4,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: (std::mem::size_of::<[f32; 4]>() * 2) as wgpu::BufferAddress,
|
||||
shader_location: 5,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: (std::mem::size_of::<[f32; 4]>() * 3) as wgpu::BufferAddress,
|
||||
shader_location: 6,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: (std::mem::size_of::<[f32; 4]>() * 4) as wgpu::BufferAddress,
|
||||
shader_location: 7,
|
||||
format: wgpu::VertexFormat::Float32,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Mesh
|
||||
{
|
||||
pub vertex_buffer: wgpu::Buffer,
|
||||
pub index_buffer: wgpu::Buffer,
|
||||
pub num_indices: u32,
|
||||
}
|
||||
|
||||
impl Mesh
|
||||
{
|
||||
pub fn new(device: &wgpu::Device, vertices: &[Vertex], indices: &[u32]) -> Self
|
||||
{
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Vertex Buffer"),
|
||||
contents: bytemuck::cast_slice(vertices),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
|
||||
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Index Buffer"),
|
||||
contents: bytemuck::cast_slice(indices),
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
});
|
||||
|
||||
Self {
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
num_indices: indices.len() as u32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_cube_mesh(device: &wgpu::Device) -> Mesh
|
||||
{
|
||||
let vertices = vec![
|
||||
Vertex {
|
||||
position: [-0.5, -0.5, 0.5],
|
||||
normal: [0.0, 0.0, 1.0],
|
||||
uv: [0.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [0.5, -0.5, 0.5],
|
||||
normal: [0.0, 0.0, 1.0],
|
||||
uv: [1.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [0.5, 0.5, 0.5],
|
||||
normal: [0.0, 0.0, 1.0],
|
||||
uv: [1.0, 1.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [-0.5, 0.5, 0.5],
|
||||
normal: [0.0, 0.0, 1.0],
|
||||
uv: [0.0, 1.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [0.5, -0.5, 0.5],
|
||||
normal: [1.0, 0.0, 0.0],
|
||||
uv: [0.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [0.5, -0.5, -0.5],
|
||||
normal: [1.0, 0.0, 0.0],
|
||||
uv: [1.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [0.5, 0.5, -0.5],
|
||||
normal: [1.0, 0.0, 0.0],
|
||||
uv: [1.0, 1.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [0.5, 0.5, 0.5],
|
||||
normal: [1.0, 0.0, 0.0],
|
||||
uv: [0.0, 1.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [0.5, -0.5, -0.5],
|
||||
normal: [0.0, 0.0, -1.0],
|
||||
uv: [0.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [-0.5, -0.5, -0.5],
|
||||
normal: [0.0, 0.0, -1.0],
|
||||
uv: [1.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [-0.5, 0.5, -0.5],
|
||||
normal: [0.0, 0.0, -1.0],
|
||||
uv: [1.0, 1.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [0.5, 0.5, -0.5],
|
||||
normal: [0.0, 0.0, -1.0],
|
||||
uv: [0.0, 1.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [-0.5, -0.5, -0.5],
|
||||
normal: [-1.0, 0.0, 0.0],
|
||||
uv: [0.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [-0.5, -0.5, 0.5],
|
||||
normal: [-1.0, 0.0, 0.0],
|
||||
uv: [1.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [-0.5, 0.5, 0.5],
|
||||
normal: [-1.0, 0.0, 0.0],
|
||||
uv: [1.0, 1.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [-0.5, 0.5, -0.5],
|
||||
normal: [-1.0, 0.0, 0.0],
|
||||
uv: [0.0, 1.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [-0.5, 0.5, 0.5],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
uv: [0.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [0.5, 0.5, 0.5],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
uv: [1.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [0.5, 0.5, -0.5],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
uv: [1.0, 1.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [-0.5, 0.5, -0.5],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
uv: [0.0, 1.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [-0.5, -0.5, -0.5],
|
||||
normal: [0.0, -1.0, 0.0],
|
||||
uv: [0.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [0.5, -0.5, -0.5],
|
||||
normal: [0.0, -1.0, 0.0],
|
||||
uv: [1.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [0.5, -0.5, 0.5],
|
||||
normal: [0.0, -1.0, 0.0],
|
||||
uv: [1.0, 1.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [-0.5, -0.5, 0.5],
|
||||
normal: [0.0, -1.0, 0.0],
|
||||
uv: [0.0, 1.0],
|
||||
},
|
||||
];
|
||||
|
||||
let indices = vec![
|
||||
0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4, 8, 9, 10, 10, 11, 8, 12, 13, 14, 14, 15, 12, 16,
|
||||
17, 18, 18, 19, 16, 20, 21, 22, 22, 23, 20,
|
||||
];
|
||||
|
||||
Mesh::new(device, &vertices, &indices)
|
||||
}
|
||||
|
||||
pub fn create_plane_mesh(
|
||||
device: &wgpu::Device,
|
||||
width: f32,
|
||||
height: f32,
|
||||
subdivisions_x: u32,
|
||||
subdivisions_y: u32,
|
||||
) -> Mesh
|
||||
{
|
||||
let mut vertices = Vec::new();
|
||||
let mut indices = Vec::new();
|
||||
|
||||
for y in 0..=subdivisions_y
|
||||
{
|
||||
for x in 0..=subdivisions_x
|
||||
{
|
||||
let fx = x as f32 / subdivisions_x as f32;
|
||||
let fy = y as f32 / subdivisions_y as f32;
|
||||
|
||||
let px = (fx - 0.5) * width;
|
||||
let py = 0.0;
|
||||
let pz = (fy - 0.5) * height;
|
||||
|
||||
vertices.push(Vertex {
|
||||
position: [px, py, pz],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
uv: [fx, fy],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for y in 0..subdivisions_y
|
||||
{
|
||||
for x in 0..subdivisions_x
|
||||
{
|
||||
let row_stride = subdivisions_x + 1;
|
||||
let i0 = y * row_stride + x;
|
||||
let i1 = i0 + 1;
|
||||
let i2 = i0 + row_stride;
|
||||
let i3 = i2 + 1;
|
||||
|
||||
indices.push(i0);
|
||||
indices.push(i2);
|
||||
indices.push(i1);
|
||||
|
||||
indices.push(i1);
|
||||
indices.push(i2);
|
||||
indices.push(i3);
|
||||
}
|
||||
}
|
||||
|
||||
Mesh::new(device, &vertices, &indices)
|
||||
}
|
||||
|
||||
pub fn create_wireframe_box(device: &wgpu::Device) -> Mesh
|
||||
{
|
||||
let vertices = vec![
|
||||
Vertex {
|
||||
position: [-0.5, -0.5, -0.5],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
uv: [0.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [0.5, -0.5, -0.5],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
uv: [0.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [0.5, -0.5, 0.5],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
uv: [0.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [-0.5, -0.5, 0.5],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
uv: [0.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [-0.5, 0.5, -0.5],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
uv: [0.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [0.5, 0.5, -0.5],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
uv: [0.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [0.5, 0.5, 0.5],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
uv: [0.0, 0.0],
|
||||
},
|
||||
Vertex {
|
||||
position: [-0.5, 0.5, 0.5],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
uv: [0.0, 0.0],
|
||||
},
|
||||
];
|
||||
|
||||
let indices = vec![
|
||||
0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7,
|
||||
];
|
||||
|
||||
Mesh::new(device, &vertices, &indices)
|
||||
}
|
||||
|
||||
pub fn load_gltf_mesh(
|
||||
device: &wgpu::Device,
|
||||
path: impl AsRef<Path>,
|
||||
) -> Result<Mesh, Box<dyn std::error::Error>>
|
||||
{
|
||||
let (gltf, buffers, _images) = gltf::import(path)?;
|
||||
|
||||
let mut all_vertices = Vec::new();
|
||||
let mut all_indices = Vec::new();
|
||||
|
||||
for scene in gltf.scenes()
|
||||
{
|
||||
for node in scene.nodes()
|
||||
{
|
||||
Self::process_node(
|
||||
&node,
|
||||
&buffers,
|
||||
Mat4::IDENTITY,
|
||||
&mut all_vertices,
|
||||
&mut all_indices,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Mesh::new(device, &all_vertices, &all_indices))
|
||||
}
|
||||
|
||||
fn process_node(
|
||||
node: &gltf::Node,
|
||||
buffers: &[gltf::buffer::Data],
|
||||
parent_transform: Mat4,
|
||||
all_vertices: &mut Vec<Vertex>,
|
||||
all_indices: &mut Vec<u32>,
|
||||
) -> Result<(), Box<dyn std::error::Error>>
|
||||
{
|
||||
let local_transform = Mat4::from_cols_array_2d(&node.transform().matrix());
|
||||
let global_transform = parent_transform * local_transform;
|
||||
|
||||
if let Some(mesh) = node.mesh()
|
||||
{
|
||||
for primitive in mesh.primitives()
|
||||
{
|
||||
let reader =
|
||||
primitive.reader(|buffer| buffers.get(buffer.index()).map(|data| &data[..]));
|
||||
|
||||
let positions = reader
|
||||
.read_positions()
|
||||
.ok_or("Missing position data")?
|
||||
.collect::<Vec<[f32; 3]>>();
|
||||
|
||||
let normals = reader
|
||||
.read_normals()
|
||||
.ok_or("Missing normal data")?
|
||||
.collect::<Vec<[f32; 3]>>();
|
||||
|
||||
let uvs = reader
|
||||
.read_tex_coords(0)
|
||||
.map(|iter| iter.into_f32().collect::<Vec<[f32; 2]>>())
|
||||
.unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]);
|
||||
|
||||
let base_index = all_vertices.len() as u32;
|
||||
|
||||
let normal_matrix = global_transform.inverse().transpose();
|
||||
|
||||
for ((pos, normal), uv) in positions.iter().zip(normals.iter()).zip(uvs.iter())
|
||||
{
|
||||
let pos_vec3 = Vec3::from(*pos);
|
||||
let normal_vec3 = Vec3::from(*normal);
|
||||
|
||||
let transformed_pos = global_transform.transform_point3(pos_vec3);
|
||||
let transformed_normal =
|
||||
normal_matrix.transform_vector3(normal_vec3).normalize();
|
||||
|
||||
all_vertices.push(Vertex {
|
||||
position: transformed_pos.into(),
|
||||
normal: transformed_normal.into(),
|
||||
uv: *uv,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(indices_reader) = reader.read_indices()
|
||||
{
|
||||
all_indices.extend(indices_reader.into_u32().map(|i| i + base_index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for child in node.children()
|
||||
{
|
||||
Self::process_node(&child, buffers, global_transform, all_vertices, all_indices)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_mesh(path: impl AsRef<Path>) -> Result<Mesh, Box<dyn std::error::Error>>
|
||||
{
|
||||
crate::render::with_device(|device| Mesh::load_gltf_mesh(device, path))
|
||||
}
|
||||
|
||||
pub fn load_gltf_with_instances(
|
||||
device: &wgpu::Device,
|
||||
path: impl AsRef<Path>,
|
||||
) -> anyhow::Result<Vec<(Mesh, Vec<InstanceData>)>>
|
||||
{
|
||||
let path = path.as_ref();
|
||||
let gltf_str = std::fs::read_to_string(path)?;
|
||||
let gltf_json: serde_json::Value = serde_json::from_str(&gltf_str)?;
|
||||
|
||||
let (document, buffers, _images) = gltf::import(path)?;
|
||||
|
||||
let mut result = Vec::new();
|
||||
|
||||
let nodes = gltf_json["nodes"]
|
||||
.as_array()
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing nodes array"))?;
|
||||
|
||||
for (node_index, json_node) in nodes.iter().enumerate()
|
||||
{
|
||||
let node = document
|
||||
.nodes()
|
||||
.nth(node_index)
|
||||
.ok_or_else(|| anyhow::anyhow!("Node index mismatch"))?;
|
||||
|
||||
if let Some(mesh_data) = node.mesh()
|
||||
{
|
||||
let has_instancing = json_node
|
||||
.get("extensions")
|
||||
.and_then(|ext| ext.get("EXT_mesh_gpu_instancing"))
|
||||
.is_some();
|
||||
|
||||
if has_instancing
|
||||
{
|
||||
let extensions = json_node.get("extensions").unwrap();
|
||||
let instancing_ext = extensions.get("EXT_mesh_gpu_instancing").unwrap();
|
||||
let mut mesh_vertices = Vec::new();
|
||||
let mut mesh_indices = Vec::new();
|
||||
|
||||
for primitive in mesh_data.primitives()
|
||||
{
|
||||
let reader = primitive
|
||||
.reader(|buffer| buffers.get(buffer.index()).map(|data| &data[..]));
|
||||
|
||||
let positions = reader
|
||||
.read_positions()
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing position data"))?
|
||||
.collect::<Vec<[f32; 3]>>();
|
||||
|
||||
let normals = reader
|
||||
.read_normals()
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing normal data"))?
|
||||
.collect::<Vec<[f32; 3]>>();
|
||||
|
||||
let uvs = reader
|
||||
.read_tex_coords(0)
|
||||
.map(|iter| iter.into_f32().collect::<Vec<[f32; 2]>>())
|
||||
.unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]);
|
||||
|
||||
let base_index = mesh_vertices.len() as u32;
|
||||
|
||||
for ((pos, normal), uv) in
|
||||
positions.iter().zip(normals.iter()).zip(uvs.iter())
|
||||
{
|
||||
mesh_vertices.push(Vertex {
|
||||
position: *pos,
|
||||
normal: *normal,
|
||||
uv: *uv,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(indices_reader) = reader.read_indices()
|
||||
{
|
||||
mesh_indices.extend(indices_reader.into_u32().map(|i| i + base_index));
|
||||
}
|
||||
}
|
||||
|
||||
let attributes = instancing_ext
|
||||
.get("attributes")
|
||||
.and_then(|v| v.as_object())
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("Missing attributes in EXT_mesh_gpu_instancing")
|
||||
})?;
|
||||
|
||||
let translation_accessor_index = attributes
|
||||
.get("TRANSLATION")
|
||||
.and_then(|v| v.as_u64())
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("Missing TRANSLATION in instancing extension")
|
||||
})? as usize;
|
||||
|
||||
let rotation_accessor_index = attributes
|
||||
.get("ROTATION")
|
||||
.and_then(|v| v.as_u64())
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("Missing ROTATION in instancing extension")
|
||||
})? as usize;
|
||||
|
||||
let scale_accessor_index = attributes
|
||||
.get("SCALE")
|
||||
.and_then(|v| v.as_u64())
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing SCALE in instancing extension"))?
|
||||
as usize;
|
||||
|
||||
let translations =
|
||||
Self::read_vec3_accessor(&document, &buffers, translation_accessor_index)?;
|
||||
let rotations =
|
||||
Self::read_quat_accessor(&document, &buffers, rotation_accessor_index)?;
|
||||
let scales =
|
||||
Self::read_vec3_accessor(&document, &buffers, scale_accessor_index)?;
|
||||
|
||||
let instances: Vec<InstanceData> = translations
|
||||
.into_iter()
|
||||
.zip(rotations.into_iter())
|
||||
.zip(scales.into_iter())
|
||||
.map(|((position, rotation), scale)| InstanceData {
|
||||
position,
|
||||
rotation,
|
||||
scale,
|
||||
dissolve_amount: 0.0,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mesh = Mesh::new(device, &mesh_vertices, &mesh_indices);
|
||||
result.push((mesh, instances));
|
||||
}
|
||||
else
|
||||
{
|
||||
let mut mesh_vertices = Vec::new();
|
||||
let mut mesh_indices = Vec::new();
|
||||
|
||||
for primitive in mesh_data.primitives()
|
||||
{
|
||||
let reader = primitive
|
||||
.reader(|buffer| buffers.get(buffer.index()).map(|data| &data[..]));
|
||||
|
||||
let positions = reader
|
||||
.read_positions()
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing position data"))?
|
||||
.collect::<Vec<[f32; 3]>>();
|
||||
|
||||
let normals = reader
|
||||
.read_normals()
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing normal data"))?
|
||||
.collect::<Vec<[f32; 3]>>();
|
||||
|
||||
let uvs = reader
|
||||
.read_tex_coords(0)
|
||||
.map(|iter| iter.into_f32().collect::<Vec<[f32; 2]>>())
|
||||
.unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]);
|
||||
|
||||
let base_index = mesh_vertices.len() as u32;
|
||||
|
||||
for ((pos, normal), uv) in
|
||||
positions.iter().zip(normals.iter()).zip(uvs.iter())
|
||||
{
|
||||
mesh_vertices.push(Vertex {
|
||||
position: *pos,
|
||||
normal: *normal,
|
||||
uv: *uv,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(indices_reader) = reader.read_indices()
|
||||
{
|
||||
mesh_indices.extend(indices_reader.into_u32().map(|i| i + base_index));
|
||||
}
|
||||
}
|
||||
|
||||
let mesh = Mesh::new(device, &mesh_vertices, &mesh_indices);
|
||||
result.push((mesh, Vec::new()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn read_vec3_accessor(
|
||||
document: &gltf::Document,
|
||||
buffers: &[gltf::buffer::Data],
|
||||
accessor_index: usize,
|
||||
) -> anyhow::Result<Vec<Vec3>>
|
||||
{
|
||||
let accessor = document
|
||||
.accessors()
|
||||
.nth(accessor_index)
|
||||
.ok_or_else(|| anyhow::anyhow!("Invalid accessor index"))?;
|
||||
|
||||
let buffer_view = accessor
|
||||
.view()
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing buffer view"))?;
|
||||
let buffer = &buffers[buffer_view.buffer().index()];
|
||||
let start = buffer_view.offset() + accessor.offset();
|
||||
let stride = buffer_view.stride().unwrap_or(12);
|
||||
|
||||
let mut result = Vec::new();
|
||||
for i in 0..accessor.count()
|
||||
{
|
||||
let offset = start + i * stride;
|
||||
let x = f32::from_le_bytes([
|
||||
buffer[offset],
|
||||
buffer[offset + 1],
|
||||
buffer[offset + 2],
|
||||
buffer[offset + 3],
|
||||
]);
|
||||
let y = f32::from_le_bytes([
|
||||
buffer[offset + 4],
|
||||
buffer[offset + 5],
|
||||
buffer[offset + 6],
|
||||
buffer[offset + 7],
|
||||
]);
|
||||
let z = f32::from_le_bytes([
|
||||
buffer[offset + 8],
|
||||
buffer[offset + 9],
|
||||
buffer[offset + 10],
|
||||
buffer[offset + 11],
|
||||
]);
|
||||
result.push(Vec3::new(x, y, z));
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn read_quat_accessor(
|
||||
document: &gltf::Document,
|
||||
buffers: &[gltf::buffer::Data],
|
||||
accessor_index: usize,
|
||||
) -> anyhow::Result<Vec<Quat>>
|
||||
{
|
||||
let accessor = document
|
||||
.accessors()
|
||||
.nth(accessor_index)
|
||||
.ok_or_else(|| anyhow::anyhow!("Invalid accessor index"))?;
|
||||
|
||||
let buffer_view = accessor
|
||||
.view()
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing buffer view"))?;
|
||||
let buffer = &buffers[buffer_view.buffer().index()];
|
||||
let start = buffer_view.offset() + accessor.offset();
|
||||
let stride = buffer_view.stride().unwrap_or(16);
|
||||
|
||||
let mut result = Vec::new();
|
||||
for i in 0..accessor.count()
|
||||
{
|
||||
let offset = start + i * stride;
|
||||
let x = f32::from_le_bytes([
|
||||
buffer[offset],
|
||||
buffer[offset + 1],
|
||||
buffer[offset + 2],
|
||||
buffer[offset + 3],
|
||||
]);
|
||||
let y = f32::from_le_bytes([
|
||||
buffer[offset + 4],
|
||||
buffer[offset + 5],
|
||||
buffer[offset + 6],
|
||||
buffer[offset + 7],
|
||||
]);
|
||||
let z = f32::from_le_bytes([
|
||||
buffer[offset + 8],
|
||||
buffer[offset + 9],
|
||||
buffer[offset + 10],
|
||||
buffer[offset + 11],
|
||||
]);
|
||||
let w = f32::from_le_bytes([
|
||||
buffer[offset + 12],
|
||||
buffer[offset + 13],
|
||||
buffer[offset + 14],
|
||||
buffer[offset + 15],
|
||||
]);
|
||||
result.push(Quat::from_xyzw(x, y, z, w));
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
6
src/loaders/mod.rs
Normal file
6
src/loaders/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub mod empty;
|
||||
pub mod heightmap;
|
||||
pub mod lights;
|
||||
pub mod mesh;
|
||||
pub mod scene;
|
||||
pub mod terrain;
|
||||
73
src/loaders/scene.rs
Normal file
73
src/loaders/scene.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use anyhow::Result;
|
||||
use glam::Vec3;
|
||||
|
||||
use crate::loaders::empty::Empties;
|
||||
use crate::loaders::lights::{LightData, Lights};
|
||||
use crate::loaders::mesh::{InstanceData, Mesh};
|
||||
use crate::render;
|
||||
|
||||
pub const CAMERA_SPAWN_OFFSET: Vec3 = Vec3::new(15.0, 15.0, 15.0);
|
||||
|
||||
pub struct Space
|
||||
{
|
||||
pub mesh_data: Vec<(Mesh, Vec<InstanceData>)>,
|
||||
pub spotlights: Vec<LightData>,
|
||||
pub player_spawn: Vec3,
|
||||
}
|
||||
|
||||
impl Space
|
||||
{
|
||||
pub fn load_space(gltf_path: &str) -> Result<Space>
|
||||
{
|
||||
let mesh_data =
|
||||
render::with_device(|device| Mesh::load_gltf_with_instances(device, gltf_path))?;
|
||||
|
||||
let lights = Lights::load_lights(gltf_path)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to load lights: {}", e))?;
|
||||
|
||||
let spotlights = lights.into_spotlights();
|
||||
|
||||
let player_spawn = Self::get_player_spawn(gltf_path)?;
|
||||
|
||||
Ok(Space {
|
||||
mesh_data,
|
||||
spotlights,
|
||||
player_spawn,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_player_spawn(gltf_path: &str) -> Result<Vec3>
|
||||
{
|
||||
let empty = Empties::get_empty_by_name(gltf_path, "PlayerSpawn")?;
|
||||
|
||||
if let Some(empty_node) = empty
|
||||
{
|
||||
let (_scale, _rotation, translation) =
|
||||
empty_node.transform.to_scale_rotation_translation();
|
||||
Ok(translation)
|
||||
}
|
||||
else
|
||||
{
|
||||
println!("Warning: PlayerSpawn empty not found, using default position");
|
||||
Ok(Vec3::new(0.0, 5.0, 0.0))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn camera_spawn_position(&self) -> Vec3
|
||||
{
|
||||
self.player_spawn + CAMERA_SPAWN_OFFSET
|
||||
}
|
||||
|
||||
pub fn terrain_mesh(&self) -> Option<&Mesh>
|
||||
{
|
||||
self.mesh_data.first().map(|(mesh, _)| mesh)
|
||||
}
|
||||
|
||||
pub fn tree_instances(&self) -> Option<(&Mesh, &Vec<InstanceData>)>
|
||||
{
|
||||
self.mesh_data
|
||||
.iter()
|
||||
.find(|(_, instances)| !instances.is_empty())
|
||||
.map(|(mesh, instances)| (mesh, instances))
|
||||
}
|
||||
}
|
||||
23
src/loaders/terrain.rs
Normal file
23
src/loaders/terrain.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use exr::prelude::{ReadChannels, ReadLayers};
|
||||
use nalgebra::DMatrix;
|
||||
|
||||
pub fn load_heightfield_from_exr(path: &str) -> anyhow::Result<DMatrix<f32>>
|
||||
{
|
||||
let image = exr::prelude::read()
|
||||
.no_deep_data()
|
||||
.largest_resolution_level()
|
||||
.all_channels()
|
||||
.all_layers()
|
||||
.all_attributes()
|
||||
.from_file(path)?;
|
||||
|
||||
let layer = &image.layer_data[0];
|
||||
let channel = &layer.channel_data.list[0];
|
||||
|
||||
let width = layer.size.width();
|
||||
let height = layer.size.height();
|
||||
|
||||
let heights: Vec<f32> = channel.sample_data.values_as_f32().collect();
|
||||
|
||||
Ok(DMatrix::from_row_slice(height, width, &heights))
|
||||
}
|
||||
Reference in New Issue
Block a user