Graphite/node-graph/gcore/src/transform.rs

265 lines
7.2 KiB
Rust

use crate::application_io::TextureFrame;
use crate::raster::bbox::AxisAlignedBbox;
use crate::raster::{ImageFrame, Pixel};
use crate::vector::VectorData;
use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup};
use glam::{DAffine2, DVec2};
pub trait Transform {
fn transform(&self) -> DAffine2;
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
pivot
}
fn decompose_scale(&self) -> DVec2 {
DVec2::new(
self.transform().transform_vector2((1., 0.).into()).length(),
self.transform().transform_vector2((0., 1.).into()).length(),
)
}
}
impl<T: Transform> Transform for &T {
fn transform(&self) -> DAffine2 {
(*self).transform()
}
}
pub trait TransformMut: Transform {
fn transform_mut(&mut self) -> &mut DAffine2;
fn translate(&mut self, offset: DVec2) {
*self.transform_mut() = DAffine2::from_translation(offset) * self.transform();
}
}
impl<P: Pixel> Transform for ImageFrame<P> {
fn transform(&self) -> DAffine2 {
self.transform
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
self.local_pivot(pivot)
}
}
impl<P: Pixel> TransformMut for ImageFrame<P> {
fn transform_mut(&mut self) -> &mut DAffine2 {
&mut self.transform
}
}
impl Transform for GraphicGroup {
fn transform(&self) -> DAffine2 {
self.transform
}
}
impl TransformMut for GraphicGroup {
fn transform_mut(&mut self) -> &mut DAffine2 {
&mut self.transform
}
}
impl Transform for GraphicElement {
fn transform(&self) -> DAffine2 {
match self {
GraphicElement::VectorData(vector_shape) => vector_shape.transform(),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform(),
GraphicElement::Raster(raster) => raster.transform(),
}
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
match self {
GraphicElement::VectorData(vector_shape) => vector_shape.local_pivot(pivot),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.local_pivot(pivot),
GraphicElement::Raster(raster) => raster.local_pivot(pivot),
}
}
}
impl TransformMut for GraphicElement {
fn transform_mut(&mut self) -> &mut DAffine2 {
match self {
GraphicElement::VectorData(vector_shape) => vector_shape.transform_mut(),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform_mut(),
GraphicElement::Raster(raster) => raster.transform_mut(),
}
}
}
impl Transform for VectorData {
fn transform(&self) -> DAffine2 {
self.transform
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
self.local_pivot(pivot)
}
}
impl TransformMut for VectorData {
fn transform_mut(&mut self) -> &mut DAffine2 {
&mut self.transform
}
}
impl Transform for Artboard {
fn transform(&self) -> DAffine2 {
DAffine2::from_translation(self.location.as_dvec2())
}
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
self.location.as_dvec2() + self.dimensions.as_dvec2() * pivot
}
}
impl Transform for DAffine2 {
fn transform(&self) -> DAffine2 {
*self
}
}
impl TransformMut for DAffine2 {
fn transform_mut(&mut self) -> &mut DAffine2 {
self
}
}
#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RenderQuality {
/// Low quality, fast rendering
Preview,
/// Ensure that the render is available with at least the specified quality
/// A value of 0.5 means that the render is available with at least 50% of the final image resolution
Scale(f32),
/// Flip a coin to decide if the render should be available with the current quality or done at full quality
/// This should be used to gradually update the render quality of a cached node
Probability(f32),
/// Render at full quality
Full,
}
#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Footprint {
/// Inverse of the transform which will be applied to the node output during the rendering process
pub transform: DAffine2,
/// Resolution of the target output area in pixels
pub resolution: glam::UVec2,
/// Quality of the render, this may be used by caching nodes to decide if the cached render is sufficient
pub quality: RenderQuality,
/// When the transform is set downstream, all upstream modifications have to be ignored
pub ignore_modifications: bool,
}
impl Default for Footprint {
fn default() -> Self {
Self {
transform: DAffine2::IDENTITY,
resolution: glam::UVec2::new(1920, 1080),
quality: RenderQuality::Full,
ignore_modifications: false,
}
}
}
impl Footprint {
pub fn viewport_bounds_in_local_space(&self) -> AxisAlignedBbox {
let inverse = self.transform.inverse();
let start = inverse.transform_point2((0., 0.).into());
let end = inverse.transform_point2(self.resolution.as_dvec2());
AxisAlignedBbox { start, end }
}
pub fn scale(&self) -> DVec2 {
self.transform.decompose_scale()
}
pub fn offset(&self) -> DVec2 {
self.transform.transform_point2(DVec2::ZERO)
}
}
impl From<()> for Footprint {
fn from(_: ()) -> Self {
Footprint::default()
}
}
#[node_macro::node(category("Debug"))]
fn cull<T>(_footprint: Footprint, #[implementations(VectorData, GraphicGroup, Artboard, ImageFrame<Color>, ArtboardGroup)] data: T) -> T {
data
}
impl core::hash::Hash for Footprint {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.transform.to_cols_array().iter().for_each(|x| x.to_le_bytes().hash(state));
self.resolution.hash(state)
}
}
impl Transform for Footprint {
fn transform(&self) -> DAffine2 {
self.transform
}
}
impl TransformMut for Footprint {
fn transform_mut(&mut self) -> &mut DAffine2 {
&mut self.transform
}
}
pub trait ApplyTransform {
fn apply_transform(&mut self, modification: &DAffine2);
}
impl<T: TransformMut> ApplyTransform for T {
fn apply_transform(&mut self, &modification: &DAffine2) {
*self.transform_mut() = self.transform() * modification
}
}
impl ApplyTransform for () {
fn apply_transform(&mut self, &_modification: &DAffine2) {}
}
#[node_macro::node(category(""))]
async fn transform<I: Into<Footprint> + 'n + ApplyTransform + Clone + Send + Sync, T: 'n + TransformMut>(
#[implementations(
(),
(),
(),
(),
Footprint,
)]
mut input: I,
#[implementations(
() -> VectorData,
() -> GraphicGroup,
() -> ImageFrame<Color>,
() -> TextureFrame,
Footprint -> VectorData,
Footprint -> GraphicGroup,
Footprint -> ImageFrame<Color>,
Footprint -> TextureFrame,
)]
transform_target: impl Node<I, Output = T>,
translate: DVec2,
rotate: f64,
scale: DVec2,
shear: DVec2,
_pivot: DVec2,
) -> T {
let modification = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]);
let footprint = input.clone().into();
if !footprint.ignore_modifications {
input.apply_transform(&modification);
}
let mut data = transform_target.eval(input).await;
let data_transform = data.transform_mut();
*data_transform = modification * (*data_transform);
data
}
#[node_macro::node(category(""))]
fn replace_transform<Data: TransformMut, TransformInput: Transform>(
_: (),
#[implementations(VectorData, ImageFrame<Color>, GraphicGroup)] mut data: Data,
#[implementations(DAffine2)] transform: TransformInput,
) -> Data {
let data_transform = data.transform_mut();
*data_transform = transform.transform();
data
}