Create new experiment, starting from previous one

This commit is contained in:
Hanno Braun 2024-12-09 19:56:50 +01:00
parent b292fcfdbc
commit e1e77f47c4
25 changed files with 4138 additions and 0 deletions

5
experiments/2024-12-09/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# Cargo
/target
# Fornjot
/*.3mf

View File

@ -0,0 +1,4 @@
{
"editor.formatOnSave": true,
"rust-analyzer.check.command": "clippy"
}

2722
experiments/2024-12-09/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
[workspace]
[package]
name = "fj"
edition = "2021"
[dependencies]
anyhow = "*"
glyphon = "*"
iter_fixed = "*"
pollster = "*"
threemf = "*"
tuples = "*"
wgpu = "*"
winit = "*"
[dependencies.bytemuck]
version = "*"
features = ["derive"]
[dependencies.glam]
version = "*"
features = ["bytemuck"]

View File

@ -0,0 +1,12 @@
{ pkgs ? import <nixpkgs> { } }:
let
libPath = with pkgs; lib.makeLibraryPath [
libxkbcommon
vulkan-loader
wayland
];
in
pkgs.mkShell {
LD_LIBRARY_PATH = "${libPath}";
}

View File

@ -0,0 +1,128 @@
use std::{collections::BTreeSet, sync::Arc};
use winit::{
application::ApplicationHandler,
event::{ElementState, KeyEvent, WindowEvent},
event_loop::{ActiveEventLoop, EventLoop},
keyboard::{Key, NamedKey},
window::{Window, WindowAttributes, WindowId},
};
use crate::{geometry::OpsLog, render::Renderer};
pub fn run(mut ops: OpsLog) -> anyhow::Result<()> {
ops.select_last();
let event_loop = EventLoop::new()?;
let mut app = App {
ops,
window: None,
renderer: None,
pressed_keys: BTreeSet::new(),
};
event_loop.run_app(&mut app)?;
Ok(())
}
struct App {
ops: OpsLog,
window: Option<Arc<Window>>,
renderer: Option<Renderer>,
pressed_keys: BTreeSet<Key>,
}
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let (window, renderer) = match init(event_loop) {
Ok(ok) => ok,
Err(err) => {
eprintln!("Initialization error: `{err:?}`");
event_loop.exit();
return;
}
};
self.window = Some(window);
self.renderer = Some(renderer);
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_: WindowId,
event: WindowEvent,
) {
let (Some(window), Some(renderer)) =
(self.window.as_ref(), self.renderer.as_mut())
else {
return;
};
match event {
WindowEvent::CloseRequested => {
event_loop.exit();
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: Key::Named(NamedKey::Escape),
..
},
..
} => {
event_loop.exit();
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key, state, ..
},
..
} => {
match state {
ElementState::Pressed => {
if self.pressed_keys.contains(&logical_key) {
return;
}
}
ElementState::Released => {
self.pressed_keys.remove(&logical_key);
return;
}
}
match logical_key {
Key::Named(NamedKey::ArrowDown) => {
self.ops.select_next();
}
Key::Named(NamedKey::ArrowUp) => {
self.ops.select_previous();
}
_ => {}
}
window.request_redraw();
}
WindowEvent::RedrawRequested => {
if let Err(err) = renderer.render(&self.ops) {
eprintln!("Render error: {err}");
}
}
_ => {}
}
}
}
fn init(
event_loop: &ActiveEventLoop,
) -> anyhow::Result<(Arc<Window>, Renderer)> {
let window = {
let window = event_loop.create_window(WindowAttributes::default())?;
Arc::new(window)
};
let renderer = pollster::block_on(Renderer::new(window.clone()))?;
Ok((window, renderer))
}

View File

@ -0,0 +1,50 @@
use std::{collections::BTreeMap, fs::File};
use crate::geometry::{Operation, OpsLog, Vertex};
pub fn export(mesh: &OpsLog) -> anyhow::Result<()> {
let mut mesh_vertices = Vec::new();
let mut mesh_triangles = Vec::new();
mesh.vertices(&mut mesh_vertices);
mesh.triangles(&mut mesh_triangles);
let mut indices_by_vertex = BTreeMap::new();
let mut vertices = Vec::new();
let mut triangles = Vec::new();
for triangle in mesh_triangles {
let triangle = triangle.vertices.map(|vertex| {
*indices_by_vertex.entry(vertex).or_insert_with(|| {
let index = vertices.len();
vertices.push(vertex);
index
})
});
triangles.push(triangle);
}
let mesh = threemf::Mesh {
vertices: threemf::model::Vertices {
vertex: vertices
.into_iter()
.map(|Vertex { point }| point)
.map(|point| point.coords.components.map(|coord| coord.value()))
.map(|[x, y, z]| threemf::model::Vertex { x, y, z })
.collect(),
},
triangles: threemf::model::Triangles {
triangle: triangles
.into_iter()
.map(|[v1, v2, v3]| threemf::model::Triangle { v1, v2, v3 })
.collect(),
},
};
let output = File::create("output.3mf")?;
threemf::write(output, mesh)?;
Ok(())
}

View File

@ -0,0 +1,9 @@
mod operation;
mod ops_log;
mod primitives;
pub use self::{
operation::Operation,
ops_log::OpsLog,
primitives::{Triangle, Vertex},
};

View File

@ -0,0 +1,8 @@
use std::fmt;
use super::{Triangle, Vertex};
pub trait Operation: fmt::Display {
fn vertices(&self, vertices: &mut Vec<Vertex>);
fn triangles(&self, triangles: &mut Vec<Triangle>);
}

View File

@ -0,0 +1,206 @@
use std::fmt;
use tuples::CombinRight;
use super::{Operation, Triangle, Vertex};
#[derive(Default)]
pub struct OpsLog {
pub operations: Vec<OperationInSequence>,
pub selected: usize,
}
impl OpsLog {
pub fn vertex(
&mut self,
vertex: impl Into<Vertex>,
) -> OperationResult<(Vertex,)> {
let vertex = vertex.into();
self.operations.push(OperationInSequence {
operation: ClonedOperation::from_op(&vertex),
previous: self
.operations
.last()
.map(|op| ClonedOperation::from_op(op)),
});
OperationResult {
operations: self,
results: (vertex,),
}
}
pub fn triangle(
&mut self,
triangle: impl Into<Triangle>,
) -> OperationResult<(Triangle,)> {
let triangle = triangle.into();
self.operations.push(OperationInSequence {
operation: ClonedOperation::from_op(&triangle),
previous: self
.operations
.last()
.map(|op| ClonedOperation::from_op(op)),
});
OperationResult {
operations: self,
results: (triangle,),
}
}
pub fn select_last(&mut self) {
self.selected = self.operations.len().saturating_sub(1);
}
pub fn select_next(&mut self) {
if self.selected < self.operations.len() {
self.selected += 1;
}
}
pub fn select_previous(&mut self) {
self.selected = self.selected.saturating_sub(1);
}
pub fn selected(&self) -> Option<&dyn Operation> {
self.operations.get(self.selected).map(|op| op as &_)
}
}
impl fmt::Display for OpsLog {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(op) = self.operations.last() {
op.fmt(f)
} else {
write!(f, "empty operations log")
}
}
}
impl Operation for OpsLog {
fn vertices(&self, vertices: &mut Vec<Vertex>) {
if let Some(op) = self.operations.last() {
op.vertices(vertices);
}
}
fn triangles(&self, triangles: &mut Vec<Triangle>) {
if let Some(op) = self.operations.last() {
op.triangles(triangles);
}
}
}
pub struct OperationInSequence {
pub operation: ClonedOperation,
pub previous: Option<ClonedOperation>,
}
impl Operation for OperationInSequence {
fn vertices(&self, vertices: &mut Vec<Vertex>) {
if let Some(op) = &self.previous {
op.vertices(vertices);
}
self.operation.vertices(vertices);
}
fn triangles(&self, triangles: &mut Vec<Triangle>) {
if let Some(op) = &self.previous {
op.triangles(triangles);
}
self.operation.triangles(triangles);
}
}
impl fmt::Display for OperationInSequence {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.operation.fmt(f)
}
}
pub struct OperationResult<'r, T> {
operations: &'r mut OpsLog,
results: T,
}
impl<'r, T> OperationResult<'r, T> {
pub fn vertex(
self,
vertex: impl Into<Vertex>,
) -> OperationResult<'r, T::Out>
where
T: CombinRight<Vertex>,
{
let OperationResult {
results: (vertex,), ..
} = self.operations.vertex(vertex);
OperationResult {
operations: self.operations,
results: self.results.push_right(vertex),
}
}
pub fn triangle(
self,
triangle: impl Into<Triangle>,
) -> OperationResult<'r, T::Out>
where
T: CombinRight<Triangle>,
{
let OperationResult {
results: (triangle,),
..
} = self.operations.triangle(triangle);
OperationResult {
operations: self.operations,
results: self.results.push_right(triangle),
}
}
pub fn results(self) -> T {
self.results
}
}
pub struct ClonedOperation {
pub description: String,
pub vertices: Vec<Vertex>,
pub triangles: Vec<Triangle>,
}
impl ClonedOperation {
pub fn from_op(op: &dyn Operation) -> Self {
let mut vertices = Vec::new();
let mut triangles = Vec::new();
op.vertices(&mut vertices);
op.triangles(&mut triangles);
Self {
description: op.to_string(),
vertices,
triangles,
}
}
}
impl fmt::Display for ClonedOperation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.description)
}
}
impl Operation for ClonedOperation {
fn vertices(&self, vertices: &mut Vec<Vertex>) {
vertices.extend(&self.vertices);
}
fn triangles(&self, triangles: &mut Vec<Triangle>) {
triangles.extend(&self.triangles);
}
}

View File

@ -0,0 +1,67 @@
use std::fmt;
use crate::math::Point;
use super::Operation;
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Vertex {
pub point: Point,
}
impl<P> From<P> for Vertex
where
P: Into<Point>,
{
fn from(point: P) -> Self {
Self {
point: point.into(),
}
}
}
impl fmt::Display for Vertex {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let [x, y, z] = self.point.coords.components.map(|s| s.value());
write!(f, "vertex {x:.2}, {y:.2}, {z:.2}")
}
}
impl Operation for Vertex {
fn vertices(&self, vertices: &mut Vec<Vertex>) {
vertices.push(*self);
}
fn triangles(&self, _: &mut Vec<Triangle>) {}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Triangle {
pub vertices: [Vertex; 3],
}
impl<V> From<[V; 3]> for Triangle
where
V: Into<Vertex>,
{
fn from(vertices: [V; 3]) -> Self {
Self {
vertices: vertices.map(Into::into),
}
}
}
impl fmt::Display for Triangle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let [a, b, c] = self.vertices;
write!(f, "triangle {a} - {b} - {c}")
}
}
impl Operation for Triangle {
fn vertices(&self, _: &mut Vec<Vertex>) {}
fn triangles(&self, triangles: &mut Vec<Triangle>) {
triangles.push(*self)
}
}

View File

@ -0,0 +1,18 @@
#![allow(clippy::module_inception)]
mod app;
mod export;
mod geometry;
mod math;
mod model;
mod render;
fn main() -> anyhow::Result<()> {
let mut ops = geometry::OpsLog::default();
model::model(&mut ops);
export::export(&ops)?;
app::run(ops)?;
Ok(())
}

View File

@ -0,0 +1,129 @@
use std::{cmp::Ordering, ops};
use iter_fixed::IntoIteratorFixed;
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Point {
pub coords: Vector,
}
impl<T> From<T> for Point
where
T: Into<Vector>,
{
fn from(coords: T) -> Self {
Self {
coords: coords.into(),
}
}
}
impl<T> ops::Add<T> for Point
where
T: Into<Vector>,
{
type Output = Self;
fn add(self, other: T) -> Self::Output {
let other = other.into();
let coords = self.coords + other;
Self { coords }
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Vector {
pub components: [Scalar; 3],
}
impl<S> From<[S; 3]> for Vector
where
S: Into<Scalar>,
{
fn from(components: [S; 3]) -> Self {
Self {
components: components.map(Into::into),
}
}
}
impl<T> ops::Add<T> for Vector
where
T: Into<Vector>,
{
type Output = Self;
fn add(self, other: T) -> Self::Output {
let other = other.into();
let components = self
.components
.into_iter_fixed()
.zip(other.components)
.map(|(a, b)| a + b)
.collect();
Self { components }
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Scalar {
value: f64,
}
impl Scalar {
pub fn new(value: f64) -> Self {
if value.is_nan() {
panic!("`Scalar` value must not be NaN");
}
if value.is_infinite() {
panic!("`Scalar` value must not be infinite. Value: `{value}`");
}
Self { value }
}
pub fn value(&self) -> f64 {
self.value
}
}
impl Eq for Scalar {}
impl Ord for Scalar {
fn cmp(&self, other: &Self) -> Ordering {
let Some(ordering) = self.value.partial_cmp(&other.value) else {
unreachable!(
"Failed to compare `Scalar` values `{}` and `{}`",
self.value, other.value
);
};
ordering
}
}
impl PartialOrd for Scalar {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl From<f64> for Scalar {
fn from(value: f64) -> Self {
Self::new(value)
}
}
impl<T> ops::Add<T> for Scalar
where
T: Into<Scalar>,
{
type Output = Self;
fn add(self, other: T) -> Self::Output {
let value = self.value() + other.into().value();
Self::new(value)
}
}

View File

@ -0,0 +1,27 @@
use crate::geometry::OpsLog;
pub fn model(ops: &mut OpsLog) {
let (a, b, c, d, e, f, g, h) = ops
.vertex([-0.5, -0.5, -0.5])
.vertex([0.5, -0.5, -0.5])
.vertex([-0.5, 0.5, -0.5])
.vertex([0.5, 0.5, -0.5])
.vertex([-0.5, -0.5, 0.5])
.vertex([0.5, -0.5, 0.5])
.vertex([-0.5, 0.5, 0.5])
.vertex([0.5, 0.5, 0.5])
.results();
ops.triangle([a, e, g]) // left
.triangle([a, g, c])
.triangle([b, d, h]) // right
.triangle([b, h, f])
.triangle([a, b, f]) // front
.triangle([a, f, e])
.triangle([c, h, d]) // back
.triangle([c, g, h])
.triangle([a, c, b]) // bottom
.triangle([b, c, d])
.triangle([e, f, h]) // top
.triangle([e, h, g]);
}

View File

@ -0,0 +1,125 @@
use std::marker::PhantomData;
use glam::Vec3;
use wgpu::util::DeviceExt;
use crate::geometry::Operation;
use super::pipelines::{triangles, vertices};
pub struct Geometry<V> {
pub vertices: wgpu::Buffer,
pub indices: wgpu::Buffer,
pub num_indices: u32,
_vertex: PhantomData<V>,
}
impl Geometry<vertices::Vertex> {
pub fn vertices(device: &wgpu::Device, operation: &dyn Operation) -> Self {
let mut mesh_vertices = Vec::new();
operation.vertices(&mut mesh_vertices);
let mut vertices = Vec::new();
let mut indices = Vec::new();
for mesh_vertex in mesh_vertices {
let s = 0.05;
let p = mesh_vertex.point;
let [a, b, c, d] = [[-s, -s], [s, -s], [-s, s], [s, s]]
.map(|[x, y]| p + [x, y, 0.])
.map(|point| {
point.coords.components.map(|scalar| scalar.value() as f32)
});
for vertex in [a, b, c, c, b, d] {
let index = vertices.len() as u32;
let vertex = vertices::Vertex {
position: vertex,
center: p.coords.components.map(|s| s.value() as f32),
radius: s as f32,
};
vertices.push(vertex);
indices.push(index);
}
}
Self::new(device, &vertices, &indices)
}
}
impl Geometry<triangles::Vertex> {
pub fn triangles(device: &wgpu::Device, operation: &dyn Operation) -> Self {
let mut mesh_triangles = Vec::new();
operation.triangles(&mut mesh_triangles);
let mut indices = Vec::new();
let mut vertices = Vec::new();
for triangle in &mesh_triangles {
let triangle = triangle.vertices.map(|vertex| {
Vec3::from(
vertex
.point
.coords
.components
.map(|coord| coord.value() as f32),
)
});
let normal = {
let [a, b, c] = triangle;
let ab = b - a;
let ac = c - a;
ab.cross(ac)
};
for point in triangle {
let index = vertices.len() as u32;
let vertex = triangles::Vertex {
position: point.into(),
normal: normal.into(),
};
indices.push(index);
vertices.push(vertex);
}
}
Self::new(device, &vertices, &indices)
}
}
impl<V> Geometry<V> {
pub fn new(device: &wgpu::Device, vertices: &[V], indices: &[u32]) -> Self
where
V: bytemuck::NoUninit,
{
let Ok(num_indices) = indices.len().try_into() else {
panic!("Unsupported number of indices: `{}`", indices.len());
};
let vertices =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
contents: bytemuck::cast_slice(vertices),
usage: wgpu::BufferUsages::VERTEX,
});
let indices =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
contents: bytemuck::cast_slice(indices),
usage: wgpu::BufferUsages::INDEX,
});
Self {
vertices,
indices,
num_indices,
_vertex: PhantomData,
}
}
}

View File

@ -0,0 +1,6 @@
mod geometry;
mod pipelines;
mod renderer;
mod text;
pub use self::renderer::Renderer;

View File

@ -0,0 +1,7 @@
mod pipeline;
mod pipelines;
pub mod triangles;
pub mod vertices;
pub use self::{pipeline::Pipeline, pipelines::Pipelines};

View File

@ -0,0 +1,129 @@
use std::marker::PhantomData;
use crate::render::geometry::Geometry;
pub struct Pipeline<V> {
render_pipeline: wgpu::RenderPipeline,
bind_group: wgpu::BindGroup,
_vertex: PhantomData<V>,
}
impl<V> Pipeline<V> {
pub fn new(
device: &wgpu::Device,
surface_configuration: &wgpu::SurfaceConfiguration,
shader_module_descriptor: wgpu::ShaderModuleDescriptor,
uniforms: &wgpu::Buffer,
) -> Self
where
V: IsVertex,
{
let bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let shader_module =
device.create_shader_module(shader_module_descriptor);
let render_pipeline =
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader_module,
entry_point: Some("vertex"),
compilation_options:
wgpu::PipelineCompilationOptions::default(),
buffers: &[wgpu::VertexBufferLayout {
array_stride: size_of::<V>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: V::ATTRIBUTES,
}],
},
fragment: Some(wgpu::FragmentState {
module: &shader_module,
entry_point: Some("fragment"),
compilation_options:
wgpu::PipelineCompilationOptions::default(),
targets: &[Some(wgpu::ColorTargetState {
format: surface_configuration.format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::all(),
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
unclipped_depth: false,
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: uniforms.as_entire_binding(),
}],
});
Pipeline {
render_pipeline,
bind_group,
_vertex: PhantomData,
}
}
pub fn draw(
&self,
render_pass: &mut wgpu::RenderPass,
geometry: &Geometry<V>,
) {
if geometry.num_indices > 0 {
render_pass.set_index_buffer(
geometry.indices.slice(..),
wgpu::IndexFormat::Uint32,
);
render_pass.set_vertex_buffer(0, geometry.vertices.slice(..));
render_pass.set_pipeline(&self.render_pipeline);
render_pass.set_bind_group(0, &self.bind_group, &[]);
render_pass.draw_indexed(0..geometry.num_indices, 0, 0..1);
}
}
}
pub trait IsVertex {
const ATTRIBUTES: &[wgpu::VertexAttribute];
}

View File

@ -0,0 +1,75 @@
use std::f32::consts::PI;
use glam::{Mat4, Vec3};
use wgpu::util::DeviceExt;
use super::{triangles, vertices, Pipeline};
pub struct Pipelines {
pub vertices: Pipeline<vertices::Vertex>,
pub triangles: Pipeline<triangles::Vertex>,
}
impl Pipelines {
pub fn new(
device: &wgpu::Device,
config: &wgpu::SurfaceConfiguration,
) -> Self {
let aspect_ratio = config.width as f32 / config.height as f32;
let uniforms =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
contents: bytemuck::cast_slice(&[Uniforms::from_transform(
default_transform(aspect_ratio),
)]),
usage: wgpu::BufferUsages::UNIFORM,
});
let vertices = Pipeline::new(
device,
config,
wgpu::include_wgsl!("shaders/vertices.wgsl"),
&uniforms,
);
let triangles = Pipeline::new(
device,
config,
wgpu::include_wgsl!("shaders/triangles.wgsl"),
&uniforms,
);
Self {
vertices,
triangles,
}
}
}
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(C)]
pub struct Uniforms {
pub transform: Mat4,
pub transform_for_normals: Mat4,
}
impl Uniforms {
pub fn from_transform(transform: Mat4) -> Self {
let transform_for_normals = transform.inverse().transpose();
Self {
transform,
transform_for_normals,
}
}
}
fn default_transform(aspect_ratio: f32) -> Mat4 {
let fov_y_radians = std::f32::consts::PI / 2.;
let z_near = 0.1;
let z_far = 10.;
Mat4::perspective_rh(fov_y_radians, aspect_ratio, z_near, z_far)
* Mat4::from_translation(Vec3::new(0., 0., -2.))
* Mat4::from_rotation_x(-PI / 4.)
* Mat4::from_rotation_z(PI / 4.)
}

View File

@ -0,0 +1,32 @@
struct Uniforms {
transform: mat4x4<f32>,
transform_for_normals: mat4x4<f32>,
};
@group(0) @binding(0)
var<uniform> uniforms: Uniforms;
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
}
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) normal: vec3<f32>,
}
@vertex
fn vertex(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.position = uniforms.transform * vec4(in.position, 1.0);
out.normal = (uniforms.transform_for_normals * vec4(in.normal, 0.0)).xyz;
return out;
}
@fragment
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
var color = vec4(in.normal, 1.0);
return color;
}

View File

@ -0,0 +1,43 @@
struct Uniforms {
transform: mat4x4<f32>,
transform_for_normals: mat4x4<f32>,
};
@group(0) @binding(0)
var<uniform> uniforms: Uniforms;
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) center: vec3<f32>,
@location(2) radius: f32,
}
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) point: vec3<f32>,
@location(1) center: vec3<f32>,
@location(2) radius: f32,
}
@vertex
fn vertex(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.position = uniforms.transform * vec4(in.position, 1.0);
out.point = in.position;
out.center = in.center;
out.radius = in.radius;
return out;
}
@fragment
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
if length(in.center - in.point) > in.radius {
discard;
}
var color = vec4(0.5, 0.5, 0.5, 1.0);
return color;
}

View File

@ -0,0 +1,15 @@
use super::pipeline::IsVertex;
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(C)]
pub struct Vertex {
pub position: [f32; 3],
pub normal: [f32; 3],
}
impl IsVertex for Vertex {
const ATTRIBUTES: &[wgpu::VertexAttribute] = &wgpu::vertex_attr_array![
0 => Float32x3,
1 => Float32x3,
];
}

View File

@ -0,0 +1,17 @@
use super::pipeline::IsVertex;
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(C)]
pub struct Vertex {
pub position: [f32; 3],
pub center: [f32; 3],
pub radius: f32,
}
impl IsVertex for Vertex {
const ATTRIBUTES: &[wgpu::VertexAttribute] = &wgpu::vertex_attr_array![
0 => Float32x3,
1 => Float32x3,
2 => Float32,
];
}

View File

@ -0,0 +1,150 @@
use std::sync::Arc;
use anyhow::anyhow;
use winit::window::Window;
use crate::geometry::OpsLog;
use super::{geometry::Geometry, pipelines::Pipelines, text::TextRenderer};
pub struct Renderer {
pub surface: wgpu::Surface<'static>,
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub surface_config: wgpu::SurfaceConfiguration,
pub pipelines: Pipelines,
pub depth_view: wgpu::TextureView,
pub text_renderer: TextRenderer,
}
impl Renderer {
pub async fn new(window: Arc<Window>) -> anyhow::Result<Self> {
let instance = wgpu::Instance::default();
let surface = instance.create_surface(window.clone())?;
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
compatible_surface: Some(&surface),
..Default::default()
})
.await
.ok_or_else(|| anyhow!("Failed to request adapter"))?;
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
required_features: wgpu::Features::default(),
required_limits: wgpu::Limits::default(),
memory_hints: wgpu::MemoryHints::default(),
},
None,
)
.await?;
let size = window.inner_size();
let surface_config = surface
.get_default_config(&adapter, size.width, size.height)
.ok_or_else(|| anyhow!("Failed to get default surface config"))?;
surface.configure(&device, &surface_config);
let pipelines = Pipelines::new(&device, &surface_config);
let depth_view = {
let depth_texture =
device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: surface_config.width,
height: surface_config.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth32Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
depth_texture.create_view(&wgpu::TextureViewDescriptor::default())
};
let text_renderer = TextRenderer::new(
&device,
&queue,
&surface_config,
window.scale_factor() as f32,
);
Ok(Self {
surface,
device,
queue,
surface_config,
pipelines,
depth_view,
text_renderer,
})
}
pub fn render(&mut self, operations: &OpsLog) -> anyhow::Result<()> {
let Some(selected_operation) = operations.selected() else {
return Ok(());
};
let vertices = Geometry::vertices(&self.device, selected_operation);
let triangles = Geometry::triangles(&self.device, selected_operation);
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
let frame = self.surface.get_current_texture().unwrap();
let color_view = frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
{
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: &color_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
store: wgpu::StoreOp::Store,
},
},
)],
depth_stencil_attachment: Some(
wgpu::RenderPassDepthStencilAttachment {
view: &self.depth_view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
},
),
timestamp_writes: None,
occlusion_query_set: None,
});
self.pipelines.vertices.draw(&mut render_pass, &vertices);
self.pipelines.triangles.draw(&mut render_pass, &triangles);
self.text_renderer.render(
operations,
&self.device,
&self.queue,
&self.surface_config,
&mut render_pass,
)?;
}
self.queue.submit(Some(encoder.finish()));
frame.present();
Ok(())
}
}

View File

@ -0,0 +1,131 @@
use crate::geometry::OpsLog;
pub struct TextRenderer {
text_atlas: glyphon::TextAtlas,
viewport: glyphon::Viewport,
text_renderer: glyphon::TextRenderer,
font_system: glyphon::FontSystem,
swash_cache: glyphon::SwashCache,
scale_factor: f32,
}
impl TextRenderer {
pub fn new(
device: &wgpu::Device,
queue: &wgpu::Queue,
surface_config: &wgpu::SurfaceConfiguration,
scale_factor: f32,
) -> Self {
let cache = glyphon::Cache::new(device);
let swash_cache = glyphon::SwashCache::new();
let mut text_atlas = glyphon::TextAtlas::new(
device,
queue,
&cache,
surface_config.format,
);
let mut viewport = glyphon::Viewport::new(device, &cache);
viewport.update(
queue,
glyphon::Resolution {
width: surface_config.width,
height: surface_config.height,
},
);
let text_renderer = glyphon::TextRenderer::new(
&mut text_atlas,
device,
wgpu::MultisampleState::default(),
Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
);
let font_system = glyphon::FontSystem::new();
Self {
text_atlas,
viewport,
text_renderer,
font_system,
swash_cache,
scale_factor,
}
}
pub fn render(
&mut self,
operations: &OpsLog,
device: &wgpu::Device,
queue: &wgpu::Queue,
surface_config: &wgpu::SurfaceConfiguration,
render_pass: &mut wgpu::RenderPass,
) -> anyhow::Result<()> {
let mut buffer = glyphon::Buffer::new(
&mut self.font_system,
glyphon::Metrics {
font_size: 16.,
line_height: 16.,
},
);
for (i, op) in operations.operations.iter().enumerate() {
let mut attrs = glyphon::Attrs::new();
if i == operations.selected {
attrs = attrs.color(glyphon::Color::rgb(0, 127, 0));
}
buffer.lines.push(glyphon::BufferLine::new(
format!("{op}"),
glyphon::cosmic_text::LineEnding::Lf,
glyphon::AttrsList::new(attrs),
glyphon::Shaping::Advanced,
));
}
buffer.shape_until_scroll(&mut self.font_system, false);
let text_area = glyphon::TextArea {
buffer: &buffer,
left: 0.,
top: 0.,
scale: self.scale_factor,
bounds: glyphon::TextBounds {
left: 0,
top: 0,
right: surface_config.width as i32,
bottom: surface_config.height as i32,
},
default_color: glyphon::Color::rgb(0, 0, 0),
custom_glyphs: &[],
};
self.text_renderer
.prepare(
device,
queue,
&mut self.font_system,
&mut self.text_atlas,
&self.viewport,
[text_area],
&mut self.swash_cache,
)
.unwrap();
self.text_renderer.render(
&self.text_atlas,
&self.viewport,
render_pass,
)?;
Ok(())
}
}