~tagglink/solar-system

85e0bfb986753ed830c86cb022500b3f3d1c48ef — Tomas Granlund 11 months ago cbbb2aa
first draft of post processing pass finished - but im getting a black screen...
M Cargo.lock => Cargo.lock +1 -32
@@ 228,28 228,6 @@ dependencies = [
]

[[package]]
name = "amethyst_gltf"
version = "0.15.3"
dependencies = [
 "amethyst_animation",
 "amethyst_assets",
 "amethyst_core",
 "amethyst_error",
 "amethyst_rendy",
 "base64 0.11.0",
 "derivative 2.2.0",
 "err-derive",
 "fnv",
 "gltf",
 "hibitset",
 "image 0.22.5",
 "itertools",
 "log",
 "mikktspace",
 "serde",
]

[[package]]
name = "amethyst_input"
version = "0.15.3"
dependencies = [


@@ 1895,15 1873,6 @@ dependencies = [
]

[[package]]
name = "mikktspace"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7525a5f1b5c369e17a4b17853a57ca05726575eee26529876d0fd8963a6e1d6"
dependencies = [
 "nalgebra",
]

[[package]]
name = "minimp3"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 3426,8 3395,8 @@ name = "solarsystem"
version = "0.1.0"
dependencies = [
 "amethyst",
 "amethyst_gltf",
 "derivative 2.2.0",
 "failure",
 "lazy_static",
 "serde",
]

M Cargo.toml => Cargo.toml +1 -1
@@ 7,8 7,8 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
amethyst_gltf = { path = "amethyst/amethyst_gltf", version = "0.15.3" }
amethyst = { path = "amethyst", version = "0.15.3", features = ["vulkan"]}
serde = { version = "1.0.104", features = ["derive"] }
lazy_static = { version = "1.4" }
derivative = { version = "2.1.1" }
failure = { version = "0.1" }

M compile-shaders.ps1 => compile-shaders.ps1 +2 -0
@@ 2,3 2,5 @@ $SDir = "src/shaders"
glslangValidator $SDir/my.conf $SDir/vertex.vert -V -o $SDir/vertex.spv
glslangValidator $SDir/my.conf $SDir/skinned_vertex.vert -V -o $SDir/skinned_vertex.spv
glslangValidator $SDir/my.conf $SDir/fragment.frag -V -o $SDir/fragment.spv
glslangValidator $SDir/my.conf $SDir/post_process_frag.frag -V -o $SDir/post_process_frag.spv
glslangValidator $SDir/my.conf $SDir/post_process_vert.vert -V -o $SDir/post_process_vert.spv

M src/body.rs => src/body.rs +76 -84
@@ 1,84 1,76 @@
use amethyst::{
  assets::PrefabData,
  core::{ecs::prelude::Entity, math::Vector3},
  derive::PrefabData,
  ecs::{Component, DenseVecStorage, WriteStorage},
  error::Error,
};
use serde::{Deserialize, Serialize};
use std::fmt::Debug;

#[derive(
  Clone, Debug, Deserialize, Serialize, PrefabData,
)]
#[serde(from = "VelocityValues", into = "VelocityValues")]
#[prefab(Component)]
pub struct Velocity {
  pub velocity: Vector3<f32>,
}

impl Component for Velocity {
  type Storage = DenseVecStorage<Self>;
}

impl Default for Velocity {
  fn default() -> Self {
    Velocity {
      velocity: Vector3::from_element(0.0),
    }
  }
}

#[derive(
  Clone, Debug, Deserialize, Serialize, PrefabData,
)]
#[serde(default)]
#[prefab(Component)]
pub struct Mass {
  pub mass: f32,
}

impl Component for Mass {
  type Storage = DenseVecStorage<Self>;
}

impl Default for Mass {
  fn default() -> Self {
    Mass { mass: 0.0 }
  }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename = "Velocity", default)]
struct VelocityValues {
  velocity: [f32; 3],
}

impl Default for VelocityValues {
  fn default() -> Self {
    VelocityValues { velocity: [0.0; 3] }
  }
}

impl From<VelocityValues> for Velocity {
  fn from(values: VelocityValues) -> Self {
    let VelocityValues { velocity } = values;
    Velocity {
      velocity: Vector3::new(
        velocity[0],
        velocity[1],
        velocity[2],
      ),
    }
  }
}

impl From<Velocity> for VelocityValues {
  fn from(component: Velocity) -> Self {
    let Velocity { velocity } = component;

    VelocityValues {
      velocity: [velocity[0], velocity[1], velocity[2]],
    }
  }
}
use amethyst::{
  assets::PrefabData,
  core::{ecs::prelude::Entity, math::Vector3},
  derive::PrefabData,
  ecs::{Component, DenseVecStorage, WriteStorage},
  error::Error,
};
use serde::{Deserialize, Serialize};
use std::fmt::Debug;

#[derive(Clone, Debug, Deserialize, Serialize, PrefabData)]
#[serde(from = "VelocityValues", into = "VelocityValues")]
#[prefab(Component)]
pub struct Velocity {
  pub velocity: Vector3<f32>,
}

impl Component for Velocity {
  type Storage = DenseVecStorage<Self>;
}

impl Default for Velocity {
  fn default() -> Self {
    Velocity {
      velocity: Vector3::from_element(0.0),
    }
  }
}

#[derive(Clone, Debug, Deserialize, Serialize, PrefabData)]
#[serde(default)]
#[prefab(Component)]
pub struct Mass {
  pub mass: f32,
}

impl Component for Mass {
  type Storage = DenseVecStorage<Self>;
}

impl Default for Mass {
  fn default() -> Self {
    Mass { mass: 0.0 }
  }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename = "Velocity", default)]
struct VelocityValues {
  velocity: [f32; 3],
}

impl Default for VelocityValues {
  fn default() -> Self {
    VelocityValues { velocity: [0.0; 3] }
  }
}

impl From<VelocityValues> for Velocity {
  fn from(values: VelocityValues) -> Self {
    let VelocityValues { velocity } = values;
    Velocity {
      velocity: Vector3::new(velocity[0], velocity[1], velocity[2]),
    }
  }
}

impl From<Velocity> for VelocityValues {
  fn from(component: Velocity) -> Self {
    let Velocity { velocity } = component;

    VelocityValues {
      velocity: [velocity[0], velocity[1], velocity[2]],
    }
  }
}

M src/main.rs => src/main.rs +1 -8
@@ 4,13 4,11 @@ use amethyst::{
  core::transform::TransformBundle,
  input::{InputBundle, StringBindings},
  prelude::*,
  renderer::{bundle::Target, plugins::RenderToWindow, types::DefaultBackend, RenderingBundle},
  renderer::{plugins::RenderToWindow, types::DefaultBackend, RenderingBundle},
  ui::{RenderUi, UiBundle},
  utils::{application_root_dir, auto_fov::AutoFovSystem},
};

use amethyst_gltf::GltfSceneLoaderSystemDesc;

mod body;
mod prefab;
mod render;


@@ 52,11 50,6 @@ fn main() -> amethyst::Result<()> {
      "gltf_prefab_loader",
      &[],
    )
    .with_system_desc(
      GltfSceneLoaderSystemDesc::default(),
      "gltf_loader",
      &["gltf_prefab_loader"],
    )
    .with_bundle(
      FlyControlBundle::<StringBindings>::new(
        Some(String::from("move_x")),

M src/prefab.rs => src/prefab.rs +44 -53
@@ 1,53 1,44 @@
use crate::body::{Mass, Velocity};
use amethyst::{
  assets::{AssetPrefab, PrefabData, ProgressCounter},
  controls::ControlTagPrefab,
  core::{ecs::prelude::Entity, Transform},
  derive::PrefabData,
  error::Error,
  renderer::{
    camera::CameraPrefab,
    formats::GraphicsPrefab,
    light::LightPrefab,
    rendy::mesh::{Normal, Position, TexCoord},
  },
  ui::{UiTransformData, UiTextData},
  utils::auto_fov::AutoFov,
};
use amethyst_gltf::{GltfSceneAsset, GltfSceneFormat};
use serde::{Deserialize, Serialize};

#[derive(Default, Deserialize, Serialize, PrefabData)]
#[serde(default)]
pub struct UiPrefab {
  transform: Option<UiTransformData<()>>,
  text: Option<UiTextData>,
}

#[derive(Default, Deserialize, Serialize, PrefabData)]
#[serde(default)]
pub struct PlanetPrefab {
  transform: Option<Transform>,
  graphics: Option<
    GraphicsPrefab<(
      Vec<Position>,
      Vec<Normal>,
      Vec<TexCoord>,
    )>,
  >,
  mass: Option<Mass>,
  velocity: Option<Velocity>,
  indicator: Option<UiPrefab>,
}

#[derive(Default, Deserialize, Serialize, PrefabData)]
#[serde(default)]
pub struct GLTFPrefab {
  transform: Option<Transform>,
  gltf:
    Option<AssetPrefab<GltfSceneAsset, GltfSceneFormat>>,
  light: Option<LightPrefab>,
  camera: Option<CameraPrefab>,
  control_tag: Option<ControlTagPrefab>,
  auto_foc: Option<AutoFov>,
}
use crate::body::{Mass, Velocity};
use amethyst::{
  assets::{PrefabData, ProgressCounter},
  controls::ControlTagPrefab,
  core::{ecs::prelude::Entity, Transform},
  derive::PrefabData,
  error::Error,
  renderer::{
    camera::CameraPrefab,
    formats::GraphicsPrefab,
    light::LightPrefab,
    rendy::mesh::{Normal, Position, TexCoord},
  },
  ui::{UiTextData, UiTransformData},
  utils::auto_fov::AutoFov,
};
use serde::{Deserialize, Serialize};

#[derive(Default, Deserialize, Serialize, PrefabData)]
#[serde(default)]
pub struct UiPrefab {
  transform: Option<UiTransformData<()>>,
  text: Option<UiTextData>,
}

#[derive(Default, Deserialize, Serialize, PrefabData)]
#[serde(default)]
pub struct PlanetPrefab {
  transform: Option<Transform>,
  graphics: Option<GraphicsPrefab<(Vec<Position>, Vec<Normal>, Vec<TexCoord>)>>,
  mass: Option<Mass>,
  velocity: Option<Velocity>,
  indicator: Option<UiPrefab>,
}

#[derive(Default, Deserialize, Serialize, PrefabData)]
#[serde(default)]
pub struct GLTFPrefab {
  transform: Option<Transform>,
  light: Option<LightPrefab>,
  camera: Option<CameraPrefab>,
  control_tag: Option<ControlTagPrefab>,
  auto_foc: Option<AutoFov>,
}

M src/render/mod.rs => src/render/mod.rs +90 -73
@@ 1,73 1,90 @@
use amethyst::renderer::{
  mtl::{TexAlbedo, TexEmission},
  pass::Base3DPassDef,
  rendy::{
    hal::pso::ShaderStageFlags,
    mesh::{
      AsVertex, Normal, Position, TexCoord, VertexFormat,
    },
    shader::SpirvShader,
  },
  skinning::JointCombined
};

mod plugin;

use lazy_static::lazy_static;

pub type RenderCustom3D = plugin::CustomRenderPlugin3D<CustomPassDef>;

lazy_static! {
  static ref CUSTOM_VERTEX: SpirvShader =
    SpirvShader::from_bytes(
      include_bytes!("../shaders/vertex.spv"),
      ShaderStageFlags::VERTEX,
      "custom",
    )
    .unwrap();
  static ref CUSTOM_SKINNED_VERTEX: SpirvShader =
    SpirvShader::from_bytes(
      include_bytes!("../shaders/skinned_vertex.spv"),
      ShaderStageFlags::VERTEX,
      "custom",
    )
    .unwrap();
  static ref CUSTOM_FRAGMENT: SpirvShader =
    SpirvShader::from_bytes(
      include_bytes!("../shaders/fragment.spv"),
      ShaderStageFlags::FRAGMENT,
      "custom",
    )
    .unwrap();
}

#[derive(Debug)]
pub struct CustomPassDef;
impl Base3DPassDef for CustomPassDef {
  const NAME: &'static str = "Custom";
  type TextureSet = (TexAlbedo, TexEmission);
  fn vertex_shader() -> &'static SpirvShader {
    &CUSTOM_VERTEX
  }
  fn vertex_skinned_shader() -> &'static SpirvShader {
    &CUSTOM_SKINNED_VERTEX
  }
  fn fragment_shader() -> &'static SpirvShader {
    &CUSTOM_FRAGMENT
  }
  fn base_format() -> Vec<VertexFormat> {
    vec![
      Position::vertex(),
      Normal::vertex(),
      TexCoord::vertex(),
    ]
  }
  fn skinned_format() -> Vec<VertexFormat> {
    vec![
      Position::vertex(),
      Normal::vertex(),
      TexCoord::vertex(),
      JointCombined::vertex(),
    ]
  }
}
use amethyst::renderer::{
  mtl::{TexAlbedo, TexEmission},
  pass::Base3DPassDef,
  rendy::{
    hal::pso::ShaderStageFlags,
    mesh::{AsVertex, Normal, Position, TexCoord, VertexFormat},
    shader::SpirvShader,
  },
  skinning::JointCombined,
};

mod plugin;
mod post_processing;

use lazy_static::lazy_static;
use post_processing::PostProcessPassDef;

pub type RenderCustom3D = plugin::CustomRenderPlugin3D<CustomPassDef, PostProcessingDef>;

lazy_static! {
  static ref CUSTOM_VERTEX: SpirvShader = SpirvShader::from_bytes(
    include_bytes!("../shaders/vertex.spv"),
    ShaderStageFlags::VERTEX,
    "custom",
  )
  .unwrap();
  static ref CUSTOM_SKINNED_VERTEX: SpirvShader = SpirvShader::from_bytes(
    include_bytes!("../shaders/skinned_vertex.spv"),
    ShaderStageFlags::VERTEX,
    "custom",
  )
  .unwrap();
  static ref CUSTOM_FRAGMENT: SpirvShader = SpirvShader::from_bytes(
    include_bytes!("../shaders/fragment.spv"),
    ShaderStageFlags::FRAGMENT,
    "custom",
  )
  .unwrap();
  static ref POST_PROCESS_VERTEX: SpirvShader = SpirvShader::from_bytes(
    include_bytes!("../shaders/post_process_vert.spv"),
    ShaderStageFlags::VERTEX,
    "custom"
  )
  .unwrap();
  static ref POST_PROCESS_FRAGMENT: SpirvShader = SpirvShader::from_bytes(
    include_bytes!("../shaders/post_process_frag.spv"),
    ShaderStageFlags::FRAGMENT,
    "custom",
  )
  .unwrap();
}

#[derive(Debug)]
pub struct CustomPassDef;
impl Base3DPassDef for CustomPassDef {
  const NAME: &'static str = "Custom";
  type TextureSet = (TexAlbedo, TexEmission);
  fn vertex_shader() -> &'static SpirvShader {
    &CUSTOM_VERTEX
  }
  fn vertex_skinned_shader() -> &'static SpirvShader {
    &CUSTOM_SKINNED_VERTEX
  }
  fn fragment_shader() -> &'static SpirvShader {
    &CUSTOM_FRAGMENT
  }
  fn base_format() -> Vec<VertexFormat> {
    vec![Position::vertex(), Normal::vertex(), TexCoord::vertex()]
  }
  fn skinned_format() -> Vec<VertexFormat> {
    vec![
      Position::vertex(),
      Normal::vertex(),
      TexCoord::vertex(),
      JointCombined::vertex(),
    ]
  }
}

#[derive(Debug)]
pub struct PostProcessingDef;
impl PostProcessPassDef for PostProcessingDef {
  const NAME: &'static str = "Post Process";
  fn vertex_shader() -> &'static SpirvShader {
    &POST_PROCESS_VERTEX
  }
  fn fragment_shader() -> &'static SpirvShader {
    &POST_PROCESS_FRAGMENT
  }
}

M src/render/plugin.rs => src/render/plugin.rs +110 -93
@@ 1,93 1,110 @@
use amethyst::{
  core::ecs::{DispatcherBuilder, World},
  error::Error,
  renderer::{
    bundle::{
      ImageOptions, OutputColor, RenderOrder, RenderPlan, RenderPlugin, Target, TargetPlanOutputs,
    },
    mtl::StaticTextureSet,
    pass::{Base3DPassDef, DrawBase3DDesc, DrawBase3DTransparentDesc},
    rendy::{mesh::VertexFormat, shader::SpirvShader},
    visibility::VisibilitySortingSystem,
    Backend, Factory, Format, Kind, RenderGroupDesc,
  },
  window::ScreenDimensions,
};

pub trait Custom3DPassDef: 'static + std::fmt::Debug + Send + Sync {
  const NAME: &'static str;
  type TextureSet: for<'a> StaticTextureSet<'a>;
  fn vertex_shader() -> &'static SpirvShader;
  fn fragment_shader() -> &'static SpirvShader;
  fn base_format() -> Vec<VertexFormat>;
  fn skinned_format() -> Vec<VertexFormat>;
}

#[derive(derivative::Derivative)]
#[derivative(Default(bound = ""), Debug(bound = ""))]
pub struct CustomRenderPlugin3D<D: Base3DPassDef> {
  dimensions: Option<ScreenDimensions>,
  dirty: bool,
  _phantomdata: std::marker::PhantomData<D>,
}

impl<B: Backend, D: Base3DPassDef> RenderPlugin<B> for CustomRenderPlugin3D<D> {
  fn on_build<'a, 'b>(
    &mut self,
    _world: &mut World,
    builder: &mut DispatcherBuilder<'a, 'b>,
  ) -> Result<(), Error> {
    builder.add(VisibilitySortingSystem::new(), "visibility_system", &[]);
    Ok(())
  }

  #[allow(clippy::map_clone)]
  fn should_rebuild(&mut self, world: &World) -> bool {
    let new_dimensions = world.try_fetch::<ScreenDimensions>();
    if self.dimensions.as_ref() != new_dimensions.as_deref() {
      self.dirty = true;
      self.dimensions = new_dimensions.map(|d| (*d).clone());
      return false;
    }
    self.dirty
  }

  fn on_plan(
    &mut self,
    plan: &mut RenderPlan<B>,
    _factory: &mut Factory<B>,
    _world: &World,
  ) -> Result<(), Error> {
    use amethyst::renderer::rendy::hal::command::{ClearColor, ClearDepthStencil, ClearValue};

    let dimensions = self.dimensions.as_ref().unwrap();
    let image_options = ImageOptions {
      kind: Kind::D2(dimensions.width() as u32, dimensions.height() as u32, 1, 1),
      levels: 1,
      format: Format::D32Sfloat,
      clear: None,
    };
    plan.define_pass(
      Target::Custom("3D"),
      TargetPlanOutputs {
        colors: vec![OutputColor::Image(ImageOptions {
          clear: Some(ClearValue::Color(ClearColor::Sfloat([0., 0., 0., 1.]))),
          ..image_options
        })],
        depth: Some(ImageOptions {
          clear: Some(ClearValue::DepthStencil(ClearDepthStencil(0.0, 0))),
          ..image_options
        }),
      },
    )?;
    plan.extend_target(Target::Custom("3D"), move |ctx| {
      ctx.add(RenderOrder::Opaque, DrawBase3DDesc::<B, D>::new().builder())?;
      ctx.add(
        RenderOrder::Transparent,
        DrawBase3DTransparentDesc::<B, D>::new().builder(),
      )?;
      Ok(())
    });
    Ok(())
  }
}
use amethyst::{
  core::ecs::{DispatcherBuilder, World},
  error::Error,
  renderer::{
    bundle::{
      ImageOptions, OutputColor, RenderOrder, RenderPlan, RenderPlugin, Target, TargetImage,
      TargetPlanOutputs,
    },
    mtl::StaticTextureSet,
    pass::{Base3DPassDef, DrawBase3DDesc, DrawBase3DTransparentDesc},
    rendy::{mesh::VertexFormat, shader::SpirvShader},
    visibility::VisibilitySortingSystem,
    Backend, Factory, Format, Kind, RenderGroupDesc,
  },
  window::ScreenDimensions,
};

use super::post_processing::{PostProcessDesc, PostProcessPassDef};

pub trait Custom3DPassDef: 'static + std::fmt::Debug + Send + Sync {
  const NAME: &'static str;
  type TextureSet: for<'a> StaticTextureSet<'a>;
  fn vertex_shader() -> &'static SpirvShader;
  fn fragment_shader() -> &'static SpirvShader;
  fn base_format() -> Vec<VertexFormat>;
  fn skinned_format() -> Vec<VertexFormat>;
}

#[derive(derivative::Derivative)]
#[derivative(Default(bound = ""), Debug(bound = ""))]
pub struct CustomRenderPlugin3D<D: Base3DPassDef, P: PostProcessPassDef> {
  dimensions: Option<ScreenDimensions>,
  dirty: bool,
  _phantomdata: std::marker::PhantomData<(D, P)>,
}

impl<B, D, P> RenderPlugin<B> for CustomRenderPlugin3D<D, P>
where
  B: Backend,
  D: Base3DPassDef,
  P: PostProcessPassDef,
{
  fn on_build<'a, 'b>(
    &mut self,
    _world: &mut World,
    builder: &mut DispatcherBuilder<'a, 'b>,
  ) -> Result<(), Error> {
    builder.add(VisibilitySortingSystem::new(), "visibility_system", &[]);
    Ok(())
  }

  #[allow(clippy::map_clone)]
  fn should_rebuild(&mut self, world: &World) -> bool {
    let new_dimensions = world.try_fetch::<ScreenDimensions>();
    if self.dimensions.as_ref() != new_dimensions.as_deref() {
      self.dirty = true;
      self.dimensions = new_dimensions.map(|d| (*d).clone());
      return false;
    }
    self.dirty
  }

  fn on_plan(
    &mut self,
    plan: &mut RenderPlan<B>,
    _factory: &mut Factory<B>,
    _world: &World,
  ) -> Result<(), Error> {
    use amethyst::renderer::rendy::hal::command::{ClearColor, ClearDepthStencil, ClearValue};

    let dimensions = self.dimensions.as_ref().unwrap();
    let image_options = ImageOptions {
      kind: Kind::D2(dimensions.width() as u32, dimensions.height() as u32, 1, 1),
      levels: 1,
      format: Format::Rgba8Srgb,
      clear: None,
    };
    plan.define_pass(
      Target::Custom("3D"),
      TargetPlanOutputs {
        colors: vec![OutputColor::Image(ImageOptions {
          clear: Some(ClearValue::Color(ClearColor::Sfloat([0., 0., 0., 1.]))),
          ..image_options
        })],
        depth: Some(ImageOptions {
          clear: Some(ClearValue::DepthStencil(ClearDepthStencil(0.0, 0))),
          format: Format::D32Sfloat,
          ..image_options
        }),
      },
    )?;
    plan.extend_target(Target::Custom("3D"), move |ctx| {
      ctx.add(RenderOrder::Opaque, DrawBase3DDesc::<B, D>::new().builder())?;
      ctx.add(
        RenderOrder::Transparent,
        DrawBase3DTransparentDesc::<B, D>::new().builder(),
      )?;
      Ok(())
    });
    plan.extend_target(Target::Main, move |ctx| {
      let image_id = ctx.get_image(TargetImage::Color(Target::Custom("3D"), 0))?;
      ctx.add(
        RenderOrder::Opaque,
        PostProcessDesc::<P>::new(image_id).builder(),
      )?;
      Ok(())
    });
    Ok(())
  }
}

A src/render/post_processing.rs => src/render/post_processing.rs +251 -0
@@ 0,0 1,251 @@
use amethyst::{
  core::ecs::World,
  renderer::{
    pipeline::{PipelineDescBuilder, PipelinesBuilder},
    rendy::{
      self,
      command::{QueueId, RenderPassEncoder},
      factory::Factory,
      graph::{
        render::{PrepareResult, RenderGroup},
        GraphContext, ImageId, NodeBuffer, NodeImage,
      },
      hal::{
        self,
        buffer::Usage,
        format::{Aspects, Format, Swizzle},
        pso, Device,
      },
      memory::Write,
      mesh::VertexFormat,
      resource::{
        Buffer, DescriptorSet, DescriptorSetLayout, Escape, Filter, Handle as RendyHandle,
        ImageView, ImageViewInfo, Sampler, SamplerInfo, SubresourceRange, ViewKind, WrapMode,
      },
      shader::{Shader, SpirvShader},
    },
    util, Backend, RenderGroupDesc,
  },
};

pub trait PostProcessPassDef: 'static + std::fmt::Debug + Send + Sync {
  /// The human readable name of this pass
  const NAME: &'static str;

  /// Returns the vertex `SpirvShader` which will be used for this pass
  fn vertex_shader() -> &'static SpirvShader;

  /// Returns the fragment `SpirvShader` which will be used for this pass
  fn fragment_shader() -> &'static SpirvShader;
}

#[derive(Debug)]
pub struct PostProcessDesc<T: PostProcessPassDef> {
  input_image: ImageId,
  _marker: std::marker::PhantomData<T>,
}

impl<T: PostProcessPassDef> PostProcessDesc<T> {
  pub fn new(input_image: ImageId) -> Self {
    PostProcessDesc {
      input_image,
      _marker: Default::default(),
    }
  }
}

impl<B: Backend, T: PostProcessPassDef> RenderGroupDesc<B, World> for PostProcessDesc<T> {
  fn build(
    self,
    ctx: &GraphContext<B>,
    factory: &mut Factory<B>,
    _queue: QueueId,
    _aux: &World,
    framebuffer_width: u32,
    framebuffer_height: u32,
    subpass: hal::pass::Subpass<'_, B>,
    _buffers: Vec<NodeBuffer>,
    _images: Vec<NodeImage>,
  ) -> Result<Box<dyn RenderGroup<B, World>>, failure::Error> {
    let layout: RendyHandle<DescriptorSetLayout<B>> = factory
      .create_descriptor_set_layout(vec![pso::DescriptorSetLayoutBinding {
        binding: 0,
        ty: pso::DescriptorType::CombinedImageSampler,
        count: 1,
        stage_flags: pso::ShaderStageFlags::FRAGMENT,
        immutable_samplers: false,
      }])?
      .into();

    let image = ctx.get_image(self.input_image).unwrap();
    let sampler_info = SamplerInfo::new(Filter::Nearest, WrapMode::Tile);
    let image_info = image.info();
    let view_info = ImageViewInfo {
      view_kind: ViewKind::D2,
      format: image_info.format,
      swizzle: Swizzle::NO,
      range: SubresourceRange {
        aspects: Aspects::COLOR,
        levels: 0..image_info.levels,
        layers: 0..1,
      },
    };
    let view = factory.create_image_view(image.clone(), view_info)?;
    let sampler = factory.create_sampler(sampler_info)?;
    let set = factory.create_descriptor_set(layout.clone())?;

    let pipeline_layout = unsafe {
      factory
        .device()
        .create_pipeline_layout(vec![layout.raw()], None as Option<(_, _)>)
    }?;

    let shader_vertex = unsafe { T::vertex_shader().module(factory).unwrap() };
    let shader_fragment = unsafe { T::fragment_shader().module(factory).unwrap() };

    let vertex_format = VertexFormat::new((Format::Rg32Sfloat, "pos"));

    let pipes = PipelinesBuilder::new()
      .with_pipeline(
        PipelineDescBuilder::new()
          .with_vertex_desc(&[(vertex_format, pso::VertexInputRate::Vertex)])
          .with_input_assembler(pso::InputAssemblerDesc::new(hal::Primitive::TriangleList))
          .with_shaders(util::simple_shader_set(
            &shader_vertex,
            Some(&shader_fragment),
          ))
          .with_layout(&pipeline_layout)
          .with_subpass(subpass)
          .with_framebuffer_size(framebuffer_width, framebuffer_height)
          .with_blend_targets(vec![pso::ColorBlendDesc {
            mask: pso::ColorMask::ALL,
            blend: Some(pso::BlendState::PREMULTIPLIED_ALPHA),
          }]), /*.with_depth_test(pso::DepthTest {
                 fun: pso::Comparison::GreaterEqual,
                 write: true,
               })*/
      )
      .build(factory, None);

    unsafe {
      factory.destroy_shader_module(shader_vertex);
      factory.destroy_shader_module(shader_fragment);
    }

    let pipeline = match pipes {
      Err(e) => {
        unsafe {
          factory.device().destroy_pipeline_layout(pipeline_layout);
        }
        return Err(e);
      }
      Ok(mut pipes) => pipes.remove(0),
    };

    Ok(Box::new(PostProcess::<B, T> {
      pipeline,
      pipeline_layout,
      vertex_buffer: None,
      img_view: view,
      img_sampler: sampler,
      desc_set: set,
      _marker: Default::default(),
    }))
  }
}

#[derive(Debug)]
pub struct PostProcess<B: Backend, T: PostProcessPassDef> {
  pipeline: B::GraphicsPipeline,
  pipeline_layout: B::PipelineLayout,
  vertex_buffer: Option<Escape<Buffer<B>>>,
  img_view: Escape<ImageView<B>>,
  img_sampler: Escape<Sampler<B>>,
  desc_set: Escape<DescriptorSet<B>>,
  _marker: std::marker::PhantomData<(B, T)>,
}

impl<B: Backend, T: PostProcessPassDef> RenderGroup<B, World> for PostProcess<B, T> {
  fn prepare(
    &mut self,
    factory: &Factory<B>,
    _queue: QueueId,
    _index: usize,
    _subpass: hal::pass::Subpass<'_, B>,
    _resources: &World,
  ) -> PrepareResult {
    let vertex_data = [
      -1.0f32, -1.0f32, 1.0f32, -1.0f32, 1.0f32, 1.0f32, -1.0f32, -1.0f32, 1.0f32, 1.0f32, -1.0f32,
      1.0f32,
    ];
    let buf_size = (core::mem::size_of::<f32>() * vertex_data.len()) as u64;

    util::ensure_buffer(
      factory,
      &mut self.vertex_buffer,
      Usage::VERTEX,
      rendy::memory::Dynamic,
      buf_size,
    )
    .unwrap();

    if let Some(buffer) = &mut self.vertex_buffer {
      let mut mapped = buffer.map(factory.device(), 0..buf_size).unwrap();
      unsafe {
        mapped
          .write::<f32>(factory.device(), 0..buf_size)
          .unwrap()
          .write(&vertex_data);
      };
    }

    let desc: pso::Descriptor<B> = pso::Descriptor::CombinedImageSampler(
      self.img_view.raw(),
      hal::image::Layout::General,
      self.img_sampler.raw(),
    );
    let write = pso::DescriptorSetWrite {
      set: self.desc_set.raw(),
      binding: 0,
      array_offset: 0,
      descriptors: Some(desc),
    };

    unsafe {
      factory.write_descriptor_sets(vec![write]);
    }

    PrepareResult::DrawRecord
  }

  fn draw_inline(
    &mut self,
    mut encoder: RenderPassEncoder<'_, B>,
    _index: usize,
    _subpass: hal::pass::Subpass<'_, B>,
    _resources: &World,
  ) {
    encoder.bind_graphics_pipeline(&self.pipeline);
    unsafe {
      if let Some(buffer) = self.vertex_buffer.as_ref() {
        encoder.bind_vertex_buffers(0, Some((buffer.raw(), 0)));
      }
      encoder.bind_graphics_descriptor_sets(
        &self.pipeline_layout,
        0,
        Some(self.desc_set.raw()),
        std::iter::empty(),
      );
      encoder.draw(0..6, 0..1);
    }
  }

  fn dispose(self: Box<Self>, factory: &mut Factory<B>, _world: &World) {
    unsafe {
      factory.device().destroy_graphics_pipeline(self.pipeline);
      factory
        .device()
        .destroy_pipeline_layout(self.pipeline_layout);
    }
  }
}

A src/shaders/post_process_frag.frag => src/shaders/post_process_frag.frag +14 -0
@@ 0,0 1,14 @@
#version 450

layout(set = 0, binding = 0) uniform sampler2D screen;

layout(location = 0) in VertexData {
    vec2 tex_uv;
} vertex;
layout(location = 0) out vec4 out_color;

void main() {
    vec4 color = texture(screen, vertex.tex_uv);
    out_color = color;
    //out_color = vec4(vertex.tex_uv, 0, 1);
}

A src/shaders/post_process_frag.spv => src/shaders/post_process_frag.spv +0 -0
A src/shaders/post_process_vert.spv => src/shaders/post_process_vert.spv +0 -0
A src/shaders/post_process_vert.vert => src/shaders/post_process_vert.vert +24 -0
@@ 0,0 1,24 @@
#version 450

layout(location = 0) in vec2 pos;

layout(location = 0) out VertexData {
    vec2 tex_uv;
} vertex;

const vec2 positions[6] = vec2[](
    vec2(-0.5, -0.5),
    vec2(0.5, -0.5),
    vec2(0.5, 0.5),
    vec2(-0.5, -0.5),
    vec2(0.5, 0.5),
    vec2(-0.5, 0.5)
);

void main() {
    float tex_u = positions[gl_VertexIndex].x + 0.5;
    float tex_v = positions[gl_VertexIndex].y + 0.5;

    vertex.tex_uv = vec2(tex_u, tex_v);
    gl_Position = vec4(pos.x, pos.y, 0.0, 1.0);
}

M src/systems/mod.rs => src/systems/mod.rs +115 -125
@@ 1,125 1,115 @@
use crate::body::{Mass, Velocity};
use amethyst::{
  core::{timing::Time, transform::Transform},
  ecs::{Join, Read, ReadStorage, System, WriteStorage},
  input::{InputHandler, StringBindings, VirtualKeyCode}
};

pub mod ui;

pub struct GravitySystem {
  time_scale: f64,
  length_scale: f64,
  mass_scale: f64,
  g: f32,
}

impl GravitySystem {
  pub fn new(time_scale: f64, length_scale: f64, mass_scale: f64) -> Self {
    GravitySystem {
      time_scale, length_scale, mass_scale,
      g: Self::calculate_g(time_scale, length_scale, mass_scale) as f32
    }
  }

  fn calculate_g(time_scale: f64, length_scale: f64, mass_scale: f64) -> f64 {
    let g = 6.67f64 * 10f64.powi(-11);
    let g = g * time_scale.powi(2);
    let g = g * mass_scale;
    let g = g / length_scale.powi(3);
    g
  }

  fn update_g(&mut self) {
    self.g = Self::calculate_g(self.time_scale, self.length_scale, self.mass_scale) as f32;
  }
}

impl Default for GravitySystem {
  fn default() -> Self {
    let time_scale = 1.0f64;
    let length_scale = 1.0f64;
    let mass_scale = 1.0f64;
    let g = Self::calculate_g(time_scale, length_scale, mass_scale);

    GravitySystem { 
      time_scale,
      length_scale,
      mass_scale,
      g: g as f32 
    }
  }
}

impl<'a> System<'a> for GravitySystem {
  type SystemData = (
    Read<'a, Time>,
    Read<'a, InputHandler<StringBindings>>,
    ReadStorage<'a, Transform>,
    ReadStorage<'a, Mass>,
    WriteStorage<'a, Velocity>,
  );

  fn run(
    &mut self,
    (time, input, transforms, masses, mut velocities): Self::SystemData,
  ) {
    if input.key_is_down(VirtualKeyCode::U) {
      self.time_scale += 1.0f64;
    }
    else if input.key_is_down(VirtualKeyCode::J) {
      if self.time_scale > 1.0f64 {
        self.time_scale -= 1.0f64;
      }
    }

    self.update_g();

    let mut vec: Vec<(&Transform, &Mass, &mut Velocity)> =
      (&transforms, &masses, &mut velocities)
        .join()
        .collect();
    for i in 0..vec.len() {
      for j in (i + 1)..vec.len() {
        let pos1 = vec[i].0.translation();
        let pos2 = vec[j].0.translation();
        let to = pos2 - pos1;
        if to.sum() == 0.0 {
          continue;
        }
        let m1 = vec[i].1.mass;
        let m2 = vec[j].1.mass;
        if m1 == 0.0 || m2 == 0.0 {
          continue;
        }
        let len_sqr = to.norm_squared();
        let f = self.g * m1 * m2 / len_sqr;
        let force_vec = to * f / len_sqr.sqrt();
        vec[i].2.velocity += (force_vec / m1) * time.delta_seconds();
        vec[j].2.velocity += (-force_vec / m2) * time.delta_seconds();
      }
    }
  }
}

pub struct BodySystem;

impl<'a> System<'a> for BodySystem {
  type SystemData = (
    Read<'a, Time>,
    WriteStorage<'a, Transform>,
    ReadStorage<'a, Velocity>,
  );

  fn run(
    &mut self,
    (time, mut transforms, velocities): Self::SystemData,
  ) {
    (&mut transforms, &velocities).join().for_each(
      |(t, b)| {
        *t.translation_mut() +=
          b.velocity * time.delta_seconds();
      },
    );
  }
}
use crate::body::{Mass, Velocity};
use amethyst::{
  core::{timing::Time, transform::Transform},
  ecs::{Join, Read, ReadStorage, System, WriteStorage},
  input::{InputHandler, StringBindings, VirtualKeyCode},
};

pub mod ui;

pub struct GravitySystem {
  time_scale: f64,
  length_scale: f64,
  mass_scale: f64,
  g: f32,
}

impl GravitySystem {
  pub fn new(time_scale: f64, length_scale: f64, mass_scale: f64) -> Self {
    GravitySystem {
      time_scale,
      length_scale,
      mass_scale,
      g: Self::calculate_g(time_scale, length_scale, mass_scale) as f32,
    }
  }

  fn calculate_g(time_scale: f64, length_scale: f64, mass_scale: f64) -> f64 {
    let g = 6.67f64 * 10f64.powi(-11);
    let g = g * time_scale.powi(2);
    let g = g * mass_scale;
    let g = g / length_scale.powi(3);
    g
  }

  fn update_g(&mut self) {
    self.g = Self::calculate_g(self.time_scale, self.length_scale, self.mass_scale) as f32;
  }
}

impl Default for GravitySystem {
  fn default() -> Self {
    let time_scale = 1.0f64;
    let length_scale = 1.0f64;
    let mass_scale = 1.0f64;
    let g = Self::calculate_g(time_scale, length_scale, mass_scale);

    GravitySystem {
      time_scale,
      length_scale,
      mass_scale,
      g: g as f32,
    }
  }
}

impl<'a> System<'a> for GravitySystem {
  type SystemData = (
    Read<'a, Time>,
    Read<'a, InputHandler<StringBindings>>,
    ReadStorage<'a, Transform>,
    ReadStorage<'a, Mass>,
    WriteStorage<'a, Velocity>,
  );

  fn run(&mut self, (time, input, transforms, masses, mut velocities): Self::SystemData) {
    if input.key_is_down(VirtualKeyCode::U) {
      self.time_scale += 1.0f64;
    } else if input.key_is_down(VirtualKeyCode::J) {
      if self.time_scale > 1.0f64 {
        self.time_scale -= 1.0f64;
      }
    }

    self.update_g();

    let mut vec: Vec<(&Transform, &Mass, &mut Velocity)> =
      (&transforms, &masses, &mut velocities).join().collect();
    for i in 0..vec.len() {
      for j in (i + 1)..vec.len() {
        let pos1 = vec[i].0.translation();
        let pos2 = vec[j].0.translation();
        let to = pos2 - pos1;
        if to.sum() == 0.0 {
          continue;
        }
        let m1 = vec[i].1.mass;
        let m2 = vec[j].1.mass;
        if m1 == 0.0 || m2 == 0.0 {
          continue;
        }
        let len_sqr = to.norm_squared();
        let f = self.g * m1 * m2 / len_sqr;
        let force_vec = to * f / len_sqr.sqrt();
        vec[i].2.velocity += (force_vec / m1) * time.delta_seconds();
        vec[j].2.velocity += (-force_vec / m2) * time.delta_seconds();
      }
    }
  }
}

pub struct BodySystem;

impl<'a> System<'a> for BodySystem {
  type SystemData = (
    Read<'a, Time>,
    WriteStorage<'a, Transform>,
    ReadStorage<'a, Velocity>,
  );

  fn run(&mut self, (time, mut transforms, velocities): Self::SystemData) {
    (&mut transforms, &velocities).join().for_each(|(t, b)| {
      *t.translation_mut() += b.velocity * time.delta_seconds();
    });
  }
}

M src/systems/ui.rs => src/systems/ui.rs +48 -58
@@ 1,58 1,48 @@
use amethyst::{
  core::{
    math::{
      base::Vector2, base::Vector4,
      geometry::Point3,
    },
    transform::Transform,
  },
  ecs::{Join, ReadStorage, System, WriteStorage},
  renderer::Camera,
  ui::UiTransform,
};

pub struct IndicatorSystem {}

impl<'a> System<'a> for IndicatorSystem {
  type SystemData = (
    ReadStorage<'a, Camera>,
    ReadStorage<'a, Transform>,
    WriteStorage<'a, UiTransform>,
  );

  fn run(
    &mut self,
    (cameras, transforms, mut ui_transforms): Self::SystemData,
  ) {
    if let Some(camera) =
      (&cameras, &transforms).join().next()
    {
      (&transforms, &mut ui_transforms).join().for_each(
        |(t, ut)| {
          let p = t.translation();
          let camera_p = camera.1.global_view_matrix()
            * Vector4::new(p.x, p.y, p.z, 1.0);
          if camera_p.z >= 0.0 {
            ut.local_x = -ut.width;
            ut.local_y = ut.height;
          }
          else {
            let screen_p = camera.0.world_to_screen(
              Point3::new(p.x, p.y, p.z),
              Vector2::new(1.0, 1.0),
              camera.1,
            );
            ut.local_x = screen_p.x;
            ut.local_y = -screen_p.y - 0.1;
          }
        },
      );
    }
  }
}

impl Default for IndicatorSystem {
  fn default() -> Self {
    IndicatorSystem {}
  }
}
use amethyst::{
  core::{
    math::{base::Vector2, base::Vector4, geometry::Point3},
    transform::Transform,
  },
  ecs::{Join, ReadStorage, System, WriteStorage},
  renderer::Camera,
  ui::UiTransform,
};

pub struct IndicatorSystem {}

impl<'a> System<'a> for IndicatorSystem {
  type SystemData = (
    ReadStorage<'a, Camera>,
    ReadStorage<'a, Transform>,
    WriteStorage<'a, UiTransform>,
  );

  fn run(&mut self, (cameras, transforms, mut ui_transforms): Self::SystemData) {
    if let Some(camera) = (&cameras, &transforms).join().next() {
      (&transforms, &mut ui_transforms)
        .join()
        .for_each(|(t, ut)| {
          let p = t.translation();
          let camera_p = camera.1.global_view_matrix() * Vector4::new(p.x, p.y, p.z, 1.0);
          if camera_p.z >= 0.0 {
            ut.local_x = -ut.width;
            ut.local_y = ut.height;
          } else {
            let screen_p = camera.0.world_to_screen(
              Point3::new(p.x, p.y, p.z),
              Vector2::new(1.0, 1.0),
              camera.1,
            );
            ut.local_x = screen_p.x;
            ut.local_y = -screen_p.y - 0.1;
          }
        });
    }
  }
}

impl Default for IndicatorSystem {
  fn default() -> Self {
    IndicatorSystem {}
  }
}

M src/ui.rs => src/ui.rs +27 -29
@@ 1,29 1,27 @@
use amethyst::{
  assets::PrefabData,
  core::ecs::prelude::Entity,
  derive::PrefabData,
  ecs::{Component, DenseVecStorage, WriteStorage},
  error::Error,
};
use serde::{Deserialize, Serialize};

#[derive(
  Clone, Debug, Deserialize, Serialize, PrefabData,
)]
#[serde(default)]
#[prefab(Component)]
pub struct Indicator {
  name: String,
}

impl Component for Indicator {
  type Storage = DenseVecStorage<Self>;
}

impl Default for Indicator {
  fn default() -> Self {
    Indicator {
      name: "Unnamed Object".to_string(),
    }
  }
}
use amethyst::{
  assets::PrefabData,
  core::ecs::prelude::Entity,
  derive::PrefabData,
  ecs::{Component, DenseVecStorage, WriteStorage},
  error::Error,
};
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Deserialize, Serialize, PrefabData)]
#[serde(default)]
#[prefab(Component)]
pub struct Indicator {
  name: String,
}

impl Component for Indicator {
  type Storage = DenseVecStorage<Self>;
}

impl Default for Indicator {
  fn default() -> Self {
    Indicator {
      name: "Unnamed Object".to_string(),
    }
  }
}

M src/universe.rs => src/universe.rs +64 -79
@@ 1,79 1,64 @@
use amethyst::{
  assets::{PrefabLoader, ProgressCounter, RonFormat},
  controls::HideCursor,
  ecs::{
    prelude::WorldExt
  },
  input::{
    is_key_down, is_mouse_button_down, VirtualKeyCode,
  },
  prelude::*,
  winit::MouseButton,
};

use crate::prefab::{GLTFPrefab, PlanetPrefab};

pub struct Universe {
  progress: Option<ProgressCounter>,
}

impl Universe {
  pub fn new() -> Self {
    Self { progress: None }
  }
}

impl SimpleState for Universe {
  fn on_start(
    &mut self,
    data: StateData<'_, GameData<'_, '_>>,
  ) {
    let StateData { world, .. } = data;
    self.progress = Some(ProgressCounter::new());

    let scene =
      world.exec(|loader: PrefabLoader<'_, GLTFPrefab>| {
        loader.load(
          "prefab/scene.ron",
          RonFormat,
          self.progress.as_mut().unwrap(),
        )
      });

    let planets =
      world.exec(|loader: PrefabLoader<'_, PlanetPrefab>| {
        loader.load(
          "prefab/planets.ron",
          RonFormat,
          self.progress.as_mut().unwrap(),
        )
      });

    world.create_entity().with(scene).build();
    world.create_entity().with(planets).build();
  }

  fn handle_event(
    &mut self,
    data: StateData<'_, GameData<'_, '_>>,
    event: StateEvent,
  ) -> Trans<GameData<'static, 'static>, StateEvent> {
    let StateData { world, .. } = data;
    if let StateEvent::Window(event) = &event {
      if is_key_down(&event, VirtualKeyCode::Escape)
      {
        let mut hide_cursor =
          world.write_resource::<HideCursor>();
        hide_cursor.hide = false;
      } else if is_mouse_button_down(
        &event,
        MouseButton::Left,
      ) {
        let mut hide_cursor =
          world.write_resource::<HideCursor>();
        hide_cursor.hide = true;
      }
    }
    Trans::None
  }
}
use amethyst::{
  assets::{PrefabLoader, ProgressCounter, RonFormat},
  controls::HideCursor,
  ecs::prelude::WorldExt,
  input::{is_key_down, is_mouse_button_down, VirtualKeyCode},
  prelude::*,
  winit::MouseButton,
};

use crate::prefab::{GLTFPrefab, PlanetPrefab};

pub struct Universe {
  progress: Option<ProgressCounter>,
}

impl Universe {
  pub fn new() -> Self {
    Self { progress: None }
  }
}

impl SimpleState for Universe {
  fn on_start(&mut self, data: StateData<'_, GameData<'_, '_>>) {
    let StateData { world, .. } = data;
    self.progress = Some(ProgressCounter::new());

    let scene = world.exec(|loader: PrefabLoader<'_, GLTFPrefab>| {
      loader.load(
        "prefab/scene.ron",
        RonFormat,
        self.progress.as_mut().unwrap(),
      )
    });

    let planets = world.exec(|loader: PrefabLoader<'_, PlanetPrefab>| {
      loader.load(
        "prefab/planets.ron",
        RonFormat,
        self.progress.as_mut().unwrap(),
      )
    });

    world.create_entity().with(scene).build();
    world.create_entity().with(planets).build();
  }

  fn handle_event(
    &mut self,
    data: StateData<'_, GameData<'_, '_>>,
    event: StateEvent,
  ) -> Trans<GameData<'static, 'static>, StateEvent> {
    let StateData { world, .. } = data;
    if let StateEvent::Window(event) = &event {
      if is_key_down(&event, VirtualKeyCode::Escape) {
        let mut hide_cursor = world.write_resource::<HideCursor>();
        hide_cursor.hide = false;
      } else if is_mouse_button_down(&event, MouseButton::Left) {
        let mut hide_cursor = world.write_resource::<HideCursor>();
        hide_cursor.hide = true;
      }
    }
    Trans::None
  }
}