Compare commits

..

No commits in common. "4b7685ae3c10747a98b87264bd2f839f4825e8a7" and "43d5259af222b0862f41ae7b6ac7d074032b8c0f" have entirely different histories.

14 changed files with 21 additions and 2499 deletions

1765
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,8 +8,3 @@ edition = "2021"
[dependencies]
glam = "0.24.1"
itertools = "0.11.0"
criterion = "0.5.1"
[[bench]]
name = "csg_bench"
harness = false

View File

@ -1,102 +0,0 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use csg::{
brush::{Brush, BrushPlane},
plane::Plane,
};
use glam::{Mat3, Vec3};
fn generate_box_brush(position: Vec3, half_extent: Vec3) -> Brush {
let normals = vec![
glam::vec3(1.0, 0.0, 0.0),
glam::vec3(-1.0, 0.0, 0.0),
glam::vec3(0.0, 1.0, 0.0),
glam::vec3(0.0, -1.0, 0.0),
glam::vec3(0.0, 0.0, 1.0),
glam::vec3(0.0, 0.0, -1.0),
];
let mut brush_planes = vec![];
for normal in &normals {
let point = position + *normal * half_extent;
brush_planes.push(BrushPlane {
plane: Plane::from_point_normal(point, *normal),
..Default::default()
});
}
Brush::new(&brush_planes)
}
/// Position is center of base plane
fn generate_pyramid_brush(position: Vec3, height: f32, side_count: u32) -> Brush {
let tip = position + Vec3::Y * height;
let base_plane = Plane::from_point_normal(position, Vec3::NEG_Y);
let mut brush_planes = vec![BrushPlane {
plane: base_plane,
..Default::default()
}];
for i in 0..side_count {
let angle = (i as f32 / side_count as f32) * 360.0_f32.to_radians();
let rot_mat = Mat3::from_cols(
Vec3::new(angle.cos(), 0.0, -angle.sin()),
Vec3::Y,
Vec3::new(angle.sin(), 0.0, angle.cos()),
);
let normal = rot_mat * Vec3::new(1.0, 1.0, 0.0);
brush_planes.push(BrushPlane {
plane: Plane::from_point_normal(tip, normal),
..Default::default()
});
}
Brush::new(&brush_planes)
}
fn get_bench_brushes() -> (Brush, Brush) {
let mut b1 = generate_pyramid_brush(Vec3::ZERO, 1.0, 64);
b1.rebuild();
let mut b2 = generate_box_brush(Vec3::new(2.0, 2.0, 0.0), Vec3::new(1.0, 1.0, 1.0));
b2.rebuild();
(b1, b2)
}
pub fn bench_aabb_intersects_with(c: &mut Criterion) {
let (b1, b2) = get_bench_brushes();
c.bench_function("AABB Intersection", |b| {
b.iter(|| b1.aabb_intersects_with(black_box(&b2)))
});
}
pub fn bench_planes_intersects_with(c: &mut Criterion) {
let (b1, b2) = get_bench_brushes();
c.bench_function("Planes Intersection", |b| {
b.iter(|| b1.planes_intersect_with(black_box(&b2)))
});
}
pub fn bench_planes_intersects_with_mat(c: &mut Criterion) {
let (b1, b2) = get_bench_brushes();
c.bench_function("Planes Intersection Matrix", |b| {
b.iter(|| b1.planes_intersect_with_mat(black_box(&b2)))
});
}
pub fn bench_planes_intersects_with_gauss(c: &mut Criterion) {
let (b1, b2) = get_bench_brushes();
c.bench_function("Planes Intersection Gaussian", |b| {
b.iter(|| b1.planes_intersect_with_gauss(black_box(&b2)))
});
}
criterion_group!(
benches,
bench_aabb_intersects_with,
bench_planes_intersects_with,
bench_planes_intersects_with_mat,
bench_planes_intersects_with_gauss
);
criterion_main!(benches);

View File

@ -1,6 +1,6 @@
use glam::Vec3;
#[derive(Default, Debug, Copy, Clone)]
#[derive(Default, Debug)]
pub struct Aabb {
pub min: Vec3,
pub max: Vec3,

View File

@ -37,7 +37,6 @@ pub struct BrushPlane {
pub tex_projection: TextureProjection,
}
#[derive(Debug, Clone)]
pub struct Brush {
pub planes: Vec<BrushPlane>,
raw_vertices: Vec<PlaneIntersection>,
@ -69,66 +68,6 @@ impl Brush {
self.aabb.intersects(other.get_aabb())
}
pub fn planes_intersect_with(&self, other: &Brush) -> bool {
// Check our vertices against their planes
for v in &self.vertices {
let mut iter = other.planes.iter();
if iter.all(|bp| bp.plane.point_in_halfspace(*v)) {
return true;
}
}
// Check their vertices against our planes
for v in &other.vertices {
let mut iter = self.planes.iter();
if iter.all(|bp| bp.plane.point_in_halfspace(*v)) {
return true;
}
}
false
}
pub fn planes_intersect_with_mat(&self, other: &Brush) -> bool {
// Check our vertices against their planes
for i in &self.raw_vertices {
let mut iter = other.planes.iter();
if iter.all(|bp| i.in_halfspace_mat(&bp.plane)) {
return true;
}
}
// Check their vertices against our planes
for i in &other.raw_vertices {
let mut iter = self.planes.iter();
if iter.all(|bp| i.in_halfspace_mat(&bp.plane)) {
return true;
}
}
false
}
pub fn planes_intersect_with_gauss(&self, other: &Brush) -> bool {
// Check our vertices against their planes
for i in &self.raw_vertices {
let mut iter = other.planes.iter();
if iter.all(|bp| i.in_halfspace_gauss(&bp.plane)) {
return true;
}
}
// Check their vertices against our planes
for i in &other.raw_vertices {
let mut iter = self.planes.iter();
if iter.all(|bp| i.in_halfspace_gauss(&bp.plane)) {
return true;
}
}
false
}
pub fn get_vertices(&self) -> Vec<Vec3> {
self.vertices.clone()
}

View File

@ -23,10 +23,6 @@ impl Plane {
}
}
pub fn point_in_halfspace(&self, point: Vec3) -> bool {
self.normal.dot(point) - self.offset <= math::EPSILON
}
pub fn into_vec4(&self) -> Vec4 {
glam::vec4(self.normal.x, self.normal.y, self.normal.z, -self.offset)
}
@ -114,11 +110,7 @@ impl PlaneIntersection {
let d1 = m1.determinant();
// We can resolve the winding order problem by multiplying by the normal matrix determinant
let m2 = glam::mat3(
self.planes[0].normal,
self.planes[1].normal,
self.planes[2].normal,
);
let m2 = self.get_matrix().transpose();
let d2 = m2.determinant();
let dist = d1 * d2;
@ -126,40 +118,8 @@ impl PlaneIntersection {
dist < math::EPSILON
}
pub fn in_halfspace_gauss(&self, plane: &Plane) -> bool {
let mut planes = [
self.planes[0].into_vec4(),
self.planes[1].into_vec4(),
self.planes[2].into_vec4(),
plane.into_vec4(),
];
// Range of 3 for x/y/z
for i in 0..3 {
let pi = planes[i];
// Find something to swap with :)
if pi[i] == 0.0 {
for j in i..3 {
// We found something to swap with!
if planes[j][i] != 0.0 {
planes.swap(i, j);
break;
}
}
}
// Do our elimination. Include r4 in this!
let a = pi[i];
for j in (i + 1)..4 {
let pj = planes[j];
if pj[i] != 0.0 {
let b = pj[i];
planes[j] = a * pj - b * pi;
}
}
}
planes[3][3] < math::EPSILON
pub fn in_halfspace(&self, plane: &Plane) -> bool {
plane.normal.dot(self.get_intersection_point()) - plane.offset <= math::EPSILON
}
fn get_matrix(&self) -> Mat3 {
@ -185,22 +145,6 @@ mod plane_tests {
assert_eq!(Plane::from_point_normal(point, normal), expected);
}
#[test]
fn point_in_halfspace() {
let intersection = PlaneIntersection::from_planes(
Plane::from_point_normal(Vec3::X, Vec3::X),
Plane::from_point_normal(Vec3::Y, Vec3::Y),
Plane::from_point_normal(Vec3::Z, Vec3::Z),
)
.unwrap();
let point = intersection.get_intersection_point();
let p1 = Plane::from_point_normal(Vec3::NEG_ONE, Vec3::NEG_ONE);
let p2 = Plane::from_point_normal(Vec3::ZERO, Vec3::ONE);
assert!(p1.point_in_halfspace(point));
assert!(!p2.point_in_halfspace(point));
}
}
#[cfg(test)]
@ -253,8 +197,21 @@ mod plane_intersection_tests {
let p1 = Plane::from_point_normal(Vec3::NEG_ONE, Vec3::NEG_ONE);
let p2 = Plane::from_point_normal(Vec3::ZERO, Vec3::ONE);
assert!(intersection.in_halfspace_mat(&p1));
assert!(intersection.in_halfspace_gauss(&p1));
assert!(!intersection.in_halfspace_mat(&p2));
assert!(!intersection.in_halfspace_gauss(&p2));
}
#[test]
fn in_half_space() {
let intersection = PlaneIntersection::from_planes(
Plane::from_point_normal(Vec3::X, Vec3::X),
Plane::from_point_normal(Vec3::Y, Vec3::Y),
Plane::from_point_normal(Vec3::Z, Vec3::Z),
)
.unwrap();
let p1 = Plane::from_point_normal(Vec3::NEG_ONE, Vec3::NEG_ONE);
let p2 = Plane::from_point_normal(Vec3::ZERO, Vec3::ONE);
assert!(intersection.in_halfspace(&p1));
assert!(!intersection.in_halfspace(&p2));
}
}

View File

@ -6,8 +6,4 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bytemuck = { version = "1.14.0", features = ["derive"] }
csg = { path = "../csg" }
log = "0.4.20"
wgpu = "0.17.0"
winit = "0.28.6"

View File

@ -1,13 +0,0 @@
mod bind_group;
mod buffer;
mod context;
mod renderer;
mod texture;
pub use self::{
bind_group::{BindGroupBuilder, BindGroupLayoutBuilder},
buffer::BulkBufferBuilder,
context::Context,
renderer::Renderer,
texture::{Texture, TextureBuilder},
};

View File

@ -1,135 +0,0 @@
use std::num::NonZeroU32;
use super::Context;
#[derive(Debug, Default)]
pub struct BindGroupLayoutBuilder<'a> {
next_binding: u32,
entries: Vec<wgpu::BindGroupLayoutEntry>,
label: Option<&'a str>,
}
impl<'a> BindGroupLayoutBuilder<'a> {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn with_label(mut self, label: &'a str) -> Self {
self.label = Some(label);
self
}
#[inline]
pub fn with_entry(
mut self,
visibility: wgpu::ShaderStages,
ty: wgpu::BindingType,
count: Option<NonZeroU32>,
) -> Self {
self.entries.push(wgpu::BindGroupLayoutEntry {
binding: self.next_binding,
visibility,
ty,
count,
});
self.next_binding += 1;
self
}
#[inline]
pub fn with_uniform_entry(self, visibility: wgpu::ShaderStages) -> Self {
self.with_entry(
visibility,
wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
None,
)
}
#[inline]
pub fn with_rw_storage_entry(self, visibility: wgpu::ShaderStages) -> Self {
self.with_entry(
visibility,
wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: false },
has_dynamic_offset: false,
min_binding_size: None,
},
None,
)
}
#[inline]
pub fn with_ro_storage_entry(self, visibility: wgpu::ShaderStages) -> Self {
self.with_entry(
visibility,
wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
None,
)
}
#[inline]
pub fn build(self, context: &Context) -> wgpu::BindGroupLayout {
context
.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: self.label,
entries: &self.entries,
})
}
}
#[derive(Debug, Default)]
pub struct BindGroupBuilder<'a> {
next_binding: u32,
label: Option<&'a str>,
entries: Vec<wgpu::BindGroupEntry<'a>>,
layout: Option<&'a wgpu::BindGroupLayout>,
}
impl<'a> BindGroupBuilder<'a> {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn with_label(mut self, label: &'a str) -> Self {
self.label = Some(label);
self
}
#[inline]
pub fn with_entry(mut self, resource: wgpu::BindingResource<'a>) -> Self {
self.entries.push(wgpu::BindGroupEntry {
binding: self.next_binding,
resource,
});
self.next_binding += 1;
self
}
#[inline]
pub fn with_layout(mut self, layout: &'a wgpu::BindGroupLayout) -> Self {
self.layout = Some(layout);
self
}
#[inline]
pub fn build(self, context: &Context) -> wgpu::BindGroup {
context
.device
.create_bind_group(&wgpu::BindGroupDescriptor {
label: self.label,
layout: self.layout.unwrap(),
entries: self.entries.as_slice(),
})
}
}

View File

@ -1,73 +0,0 @@
use bytemuck::NoUninit;
use wgpu::util::DeviceExt;
use super::Context;
#[derive(Debug)]
pub struct BulkBufferBuilder<'a> {
order: Vec<(bool, usize)>,
init_descriptors: Vec<wgpu::util::BufferInitDescriptor<'a>>,
descriptors: Vec<wgpu::BufferDescriptor<'a>>,
current_usage: wgpu::BufferUsages,
}
impl<'a> BulkBufferBuilder<'a> {
pub fn new() -> Self {
Self {
order: vec![],
init_descriptors: vec![],
descriptors: vec![],
current_usage: wgpu::BufferUsages::UNIFORM,
}
}
pub fn set_usage(mut self, usage: wgpu::BufferUsages) -> Self {
self.current_usage = usage;
self
}
pub fn with_buffer(mut self, label: &'a str, size: u64, mapped: bool) -> Self {
let descriptor = wgpu::BufferDescriptor {
label: Some(label),
size,
usage: self.current_usage,
mapped_at_creation: mapped,
};
self.order.push((false, self.descriptors.len()));
self.descriptors.push(descriptor);
self
}
pub fn with_init_buffer(mut self, label: &'a str, contents: &'a [u8]) -> Self {
let descriptor = wgpu::util::BufferInitDescriptor {
label: Some(label),
contents,
usage: self.current_usage,
};
self.order.push((true, self.init_descriptors.len()));
self.init_descriptors.push(descriptor);
self
}
pub fn with_init_buffer_bm<A: NoUninit>(self, label: &'a str, contents: &'a [A]) -> Self {
self.with_init_buffer(label, bytemuck::cast_slice(contents))
}
pub fn build(self, context: &Context) -> Vec<wgpu::Buffer> {
let device = &context.device;
let mut buffers = vec![];
for (init, index) in self.order {
let buffer = if init {
device.create_buffer_init(&(self.init_descriptors[index]))
} else {
device.create_buffer(&(self.descriptors[index]))
};
buffers.push(buffer);
}
buffers
}
}

View File

@ -1,69 +0,0 @@
use winit::{dpi::PhysicalSize, window::Window};
pub struct Context {
pub instance: wgpu::Instance,
pub size: PhysicalSize<u32>,
pub surface: wgpu::Surface,
pub surface_config: wgpu::SurfaceConfiguration,
pub adapter: wgpu::Adapter,
pub device: wgpu::Device,
pub queue: wgpu::Queue,
}
impl Context {
pub async fn new(window: &Window, limits: wgpu::Limits) -> Self {
log::info!("Initialising WGPU context...");
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::VULKAN,
dx12_shader_compiler: Default::default(),
});
// To be able to start drawing we need a few things:
// - A surface
// - A GPU device to draw to the surface
// - A draw command queue
log::info!("Initialising window surface...");
let surface = unsafe { instance.create_surface(&window) }.unwrap();
log::info!("Requesting GPU adapter...");
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
force_fallback_adapter: false,
compatible_surface: Some(&surface),
})
.await
.unwrap();
log::info!("Checking GPU adapter meets requirements");
log::info!("Requesting GPU device...");
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: wgpu::Features::empty(),
limits,
},
None,
)
.await
.unwrap();
log::info!("Configuring window surface...");
let size = window.inner_size();
let surface_config = surface
.get_default_config(&adapter, size.width, size.height)
.unwrap();
surface.configure(&device, &surface_config);
Self {
instance,
size,
surface,
surface_config,
adapter,
device,
queue,
}
}
}

View File

@ -1,6 +0,0 @@
use std::time::Duration;
pub trait Renderer {
fn update(&mut self, dt: &Duration, context: &super::Context);
fn render(&self, context: &super::Context);
}

View File

@ -1,198 +0,0 @@
// TODO: Support mip-mapping and multi-sampling
use super::{BindGroupBuilder, BindGroupLayoutBuilder, Context};
#[derive(Debug, Clone)]
pub struct TextureAttributes {
pub size: wgpu::Extent3d,
pub dimension: wgpu::TextureDimension,
pub format: wgpu::TextureFormat,
pub usage: wgpu::TextureUsages,
pub address_mode_u: wgpu::AddressMode,
pub address_mode_v: wgpu::AddressMode,
pub address_mode_w: wgpu::AddressMode,
pub mag_filter: wgpu::FilterMode,
pub min_filter: wgpu::FilterMode,
pub mipmap_filter: wgpu::FilterMode,
pub shader_visibility: wgpu::ShaderStages,
}
impl Default for TextureAttributes {
fn default() -> Self {
Self {
size: Default::default(),
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
address_mode_u: wgpu::AddressMode::default(),
address_mode_v: wgpu::AddressMode::default(),
address_mode_w: wgpu::AddressMode::default(),
mag_filter: wgpu::FilterMode::default(),
min_filter: wgpu::FilterMode::default(),
mipmap_filter: wgpu::FilterMode::default(),
shader_visibility: wgpu::ShaderStages::FRAGMENT,
}
}
}
#[derive(Debug)]
pub struct TextureBuilder {
pub attributes: TextureAttributes,
}
impl TextureBuilder {
pub fn new() -> Self {
Self {
attributes: Default::default(),
}
}
#[inline]
pub fn with_size(mut self, width: u32, height: u32, depth: u32) -> Self {
self.attributes.size = wgpu::Extent3d {
width,
height,
depth_or_array_layers: depth,
};
self
}
#[inline]
pub fn with_dimension(mut self, dimension: wgpu::TextureDimension) -> Self {
self.attributes.dimension = dimension;
self
}
#[inline]
pub fn with_format(mut self, format: wgpu::TextureFormat) -> Self {
self.attributes.format = format;
self
}
#[inline]
pub fn with_usage(mut self, usage: wgpu::TextureUsages) -> Self {
self.attributes.usage = usage;
self
}
#[inline]
pub fn with_address_mode(mut self, address_mode: wgpu::AddressMode) -> Self {
self.attributes.address_mode_u = address_mode;
self.attributes.address_mode_v = address_mode;
self.attributes.address_mode_w = address_mode;
self
}
#[inline]
pub fn with_filter_mode(mut self, filter_mode: wgpu::FilterMode) -> Self {
self.attributes.mag_filter = filter_mode;
self.attributes.min_filter = filter_mode;
self.attributes.mipmap_filter = filter_mode;
self
}
#[inline]
pub fn with_shader_visibility(mut self, visibility: wgpu::ShaderStages) -> Self {
self.attributes.shader_visibility = visibility;
self
}
#[inline]
pub fn build(self, context: &Context) -> Texture {
Texture::new(context, self.attributes)
}
}
#[derive(Debug)]
pub struct Texture {
pub attributes: TextureAttributes,
pub texture: wgpu::Texture,
pub view: wgpu::TextureView,
pub sampler: wgpu::Sampler,
pub bind_group_layout: wgpu::BindGroupLayout,
pub bind_group: wgpu::BindGroup,
}
impl Texture {
pub fn new(context: &Context, attributes: TextureAttributes) -> Self {
let texture = context.device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: attributes.size,
mip_level_count: 1,
sample_count: 1,
dimension: attributes.dimension,
format: attributes.format,
usage: attributes.usage,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = context.device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: attributes.address_mode_u,
address_mode_v: attributes.address_mode_v,
address_mode_w: attributes.address_mode_w,
mag_filter: attributes.mag_filter,
min_filter: attributes.min_filter,
mipmap_filter: attributes.mipmap_filter,
..Default::default()
});
let view_dimension = match attributes.dimension {
wgpu::TextureDimension::D1 => wgpu::TextureViewDimension::D1,
wgpu::TextureDimension::D2 => wgpu::TextureViewDimension::D2,
wgpu::TextureDimension::D3 => wgpu::TextureViewDimension::D3,
};
let bind_group_layout = BindGroupLayoutBuilder::new()
.with_entry(
attributes.shader_visibility,
wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension,
multisampled: false,
},
None,
)
.with_entry(
attributes.shader_visibility,
wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
None,
)
.build(context);
let bind_group = BindGroupBuilder::new()
.with_layout(&bind_group_layout)
.with_entry(wgpu::BindingResource::TextureView(&view))
.with_entry(wgpu::BindingResource::Sampler(&sampler))
.build(context);
Self {
attributes,
texture,
view,
sampler,
bind_group_layout,
bind_group,
}
}
pub fn update(&self, context: &Context, data: &[u8]) {
log::info!("Updating texture contents...");
let copy_texture = wgpu::ImageCopyTexture {
texture: &self.texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
};
let size = self.attributes.size;
let image_layout = wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4 * size.width),
rows_per_image: Some(size.height),
};
context
.queue
.write_texture(copy_texture, data, image_layout, size);
}
}

View File

@ -1,14 +1,10 @@
mod gfx;
use std::time::Instant;
use csg::{
brush::{Brush, BrushPlane},
glam::{self, Mat3, Vec3},
plane::Plane,
};
fn generate_box_brush(position: Vec3, half_extent: Vec3) -> Brush {
fn generate_box_brush(position: Vec3, extent: Vec3) -> Brush {
let normals = vec![
glam::vec3(1.0, 0.0, 0.0),
glam::vec3(-1.0, 0.0, 0.0),
@ -20,7 +16,7 @@ fn generate_box_brush(position: Vec3, half_extent: Vec3) -> Brush {
let mut brush_planes = vec![];
for normal in &normals {
let point = position + *normal * half_extent;
let point = position + *normal * extent;
brush_planes.push(BrushPlane {
plane: Plane::from_point_normal(point, *normal),