mirror of https://github.com/hannobraun/Fornjot
Create new experiment, starting from previous one
This commit is contained in:
parent
b292fcfdbc
commit
e1e77f47c4
|
@ -0,0 +1,5 @@
|
||||||
|
# Cargo
|
||||||
|
/target
|
||||||
|
|
||||||
|
# Fornjot
|
||||||
|
/*.3mf
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"rust-analyzer.check.command": "clippy"
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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"]
|
|
@ -0,0 +1,12 @@
|
||||||
|
{ pkgs ? import <nixpkgs> { } }:
|
||||||
|
|
||||||
|
let
|
||||||
|
libPath = with pkgs; lib.makeLibraryPath [
|
||||||
|
libxkbcommon
|
||||||
|
vulkan-loader
|
||||||
|
wayland
|
||||||
|
];
|
||||||
|
in
|
||||||
|
pkgs.mkShell {
|
||||||
|
LD_LIBRARY_PATH = "${libPath}";
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
|
@ -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(())
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
mod operation;
|
||||||
|
mod ops_log;
|
||||||
|
mod primitives;
|
||||||
|
|
||||||
|
pub use self::{
|
||||||
|
operation::Operation,
|
||||||
|
ops_log::OpsLog,
|
||||||
|
primitives::{Triangle, Vertex},
|
||||||
|
};
|
|
@ -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>);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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(())
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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]);
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
mod geometry;
|
||||||
|
mod pipelines;
|
||||||
|
mod renderer;
|
||||||
|
mod text;
|
||||||
|
|
||||||
|
pub use self::renderer::Renderer;
|
|
@ -0,0 +1,7 @@
|
||||||
|
mod pipeline;
|
||||||
|
mod pipelines;
|
||||||
|
|
||||||
|
pub mod triangles;
|
||||||
|
pub mod vertices;
|
||||||
|
|
||||||
|
pub use self::{pipeline::Pipeline, pipelines::Pipelines};
|
|
@ -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];
|
||||||
|
}
|
|
@ -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.)
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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,
|
||||||
|
];
|
||||||
|
}
|
|
@ -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,
|
||||||
|
];
|
||||||
|
}
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue