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