This commit is contained in:
Tsuki 2024-09-13 09:53:17 +08:00
parent b588115b7d
commit 664223d199
28 changed files with 1805 additions and 110 deletions

1
Cargo.lock generated
View File

@ -1821,6 +1821,7 @@ dependencies = [
"flate2",
"freetype-rs",
"geo 0.28.0",
"geo-macros",
"glow",
"glsl",
"glsl-quasiquote",

View File

@ -215,3 +215,44 @@ levels = [
6.5,
7,
]
[[cmap]]
type = "VIL"
levels = [
0.1,
1,
2.5,
5,
7.5,
10,
10.25,
15,
20,
25,
30,
35,
40,
45,
50,
55,
104,
]
colors = [
'#484892',
'#01a0f6',
'#00ecec',
'#01ff00',
'#00c800',
'#019000',
'#ffff00',
'#e7c000',
'#ff9000',
'#ff0000',
'#d60000',
'#c00000',
'#ff00f0',
'#ad90f0',
'#780084',
'#d8af97',
]

View File

@ -5,14 +5,21 @@ use proc_macro2::Literal;
use proc_macro2::TokenStream as TokenStream2;
use quote::format_ident;
use quote::quote;
use quote::ToTokens;
use regex::Regex;
use syn::parse_macro_input;
use syn::parse_str;
use syn::Expr;
use syn::Fields::Named;
use syn::FnArg;
use syn::ImplItem;
use syn::ImplItemFn;
use syn::ItemImpl;
use syn::ItemStruct;
use syn::Lit;
use syn::LitStr;
use syn::Receiver;
use syn::Type;
#[proc_macro_derive(Prj)]
pub fn prj_macro(input: TokenStream) -> TokenStream {
@ -202,3 +209,143 @@ fn litstr_to_format(
.into()
});
}
#[proc_macro_derive(ModuleRef)]
pub fn module_ref_macro(input: TokenStream) -> TokenStream {
let struct_item = parse_macro_input!(input as ItemStruct);
let stru_name = struct_item.ident;
let visi = struct_item.vis;
let generics = struct_item.generics;
match struct_item.fields {
Named(fields) => {
let ref_fields = fields.named.iter().map(|field| {
let field_name = &field.ident;
let field_ty = &field.ty;
// 如果字段是 &mut T 类型,则将其改为 &T
let new_field_ty = if let syn::Type::Reference(syn::TypeReference {
mutability: Some(_),
elem,
and_token,
lifetime,
}) = field_ty
{
let new_ty = syn::TypeReference {
mutability: None, // 移除 mutability
elem: elem.clone(),
and_token: and_token.clone(),
lifetime: lifetime.clone(),
};
syn::Type::Reference(new_ty)
} else {
field_ty.clone() // 不是 &mut T 类型的字段保持不变
};
// 生成字段
quote! {
#field_name: #new_field_ty
}
});
let ref_name = format_ident!("{}Ref", &stru_name);
quote!(
#[derive(Clone, Copy)]
#visi struct #ref_name #generics {
#(#ref_fields),*
}
)
.into()
}
_ => quote!(
struct A {}
)
.into(),
}
}
// 定义过程宏,用于将 `A` 类型的 `impl` 块复制为 `ARef`
#[proc_macro_attribute]
pub fn module_ref_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
// 解析输入的 `impl` 块
let input = parse_macro_input!(item as ItemImpl);
// 获取目标类型 `A` 的名称
let original_ty = input.self_ty.as_ref();
// 将 `A` 替换为 `ARef`
let ref_ty = transform_to_ref_type(original_ty);
// 提取泛型参数和生命周期
let generics = &input.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
// 遍历 `impl` 中的方法,复制所有 `&self` 的方法
let methods: Vec<_> = input
.items
.iter()
.filter_map(|item| {
if let ImplItem::Fn(method) = item {
if has_self_receiver(&method) {
// 如果方法包含 `&self`,则复制它
Some(method)
} else {
None
}
} else {
None
}
})
.collect();
// 构建新的 `impl` 块,针对 `ARef`
let expanded = quote! {
// 原始的 `impl` 块
#input
// 新的 `impl` 块,针对 `ARef`
impl #impl_generics EarthModuleRef #ty_generics #where_clause {
#(#methods)*
}
};
// 返回生成的代码
TokenStream::from(expanded)
}
// 将类型 `A` 转换为 `ARef`
fn transform_to_ref_type(ty: &Type) -> Type {
if let Type::Path(type_path) = ty {
let last_segment = type_path.path.segments.last().unwrap();
// 构造新的类型名,将 `A` 转换为 `ARef`
let new_ident = format_ident!("{}Ref", last_segment.ident);
// 创建新的类型路径,保留泛型参数和生命周期
let mut new_segment = last_segment.clone();
new_segment.ident = new_ident;
let mut new_type_path = type_path.clone();
new_type_path.path.segments.pop();
new_type_path.path.segments.push(new_segment);
Type::Path(new_type_path)
} else {
panic!("Expected a path type for the struct");
}
}
// 判断方法是否包含 `&self`
fn has_self_receiver(method: &syn::ImplItemFn) -> bool {
method.sig.inputs.iter().any(|arg| match arg {
FnArg::Receiver(Receiver {
reference: Some(_),
mutability: None,
..
}) => true,
_ => false,
})
}

View File

@ -60,3 +60,6 @@ name = "gi"
[dependencies.radarg_core]
path = "../radarg_core"
[dependencies.geo-macros]
path = "../geo-macros"

View File

@ -3,6 +3,7 @@ pub mod colormap;
mod colormesh;
pub mod font;
pub mod geoquadmesh;
pub mod planet;
pub mod ppi;
pub mod threed;
pub mod tools;

View File

@ -0,0 +1,203 @@
use glow::HasContext;
use crate::components::Program;
use crate::errors::Result;
use crate::graphics::{AttaWithBuffer, Graphics};
use crate::pg::Attach;
use crate::shaders::earth::EarthFragment;
use crate::GL;
use crate::{components::Shader, shaders::earth::EarthVertex};
pub struct Earth {
program: Program,
// Buffers
attach: Attach,
}
impl Earth {
pub fn new(gl: &GL) -> Result<Self> {
let vertex = Shader::new(glow::VERTEX_SHADER, EarthVertex::new())?;
// let geometry = Shader::new(glow::GEOMETRY_SHADER, EarthGeometry::new())?;
let fragment = Shader::new(glow::FRAGMENT_SHADER, EarthFragment::new())?;
let config = EarthConfig::default();
let default_attach = config.create_attach(gl);
let program = Program::new(vertex, fragment, None, "330 core");
Ok(Self {
program,
attach: default_attach,
})
}
fn set_conf(&mut self, gl: &GL, config: &EarthConfig) -> Result<()> {
unsafe {
let sun_loc = self.program.get_uniform_location(gl, "sun_location");
gl.uniform_3_f32_slice(sun_loc.as_ref(), &config.sun_location);
let er = self.program.get_uniform_location(gl, "ER");
gl.uniform_1_f32(er.as_ref(), config.earth_radius);
}
Ok(())
}
}
pub struct EarthConfig {
pub earth_radius: f32,
pub sun_location: [f32; 3],
}
impl EarthConfig {
fn create_attach(&self, gl: &GL) -> Attach {
unsafe {
let vao = gl.create_vertex_array().unwrap();
gl.bind_vertex_array(Some(vao));
let vbo = gl.create_buffer().unwrap();
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo));
gl.enable_vertex_attrib_array(0);
gl.vertex_attrib_pointer_f32(0, 3, glow::FLOAT, false, 12, 0);
let ebo = gl.create_buffer().unwrap();
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(ebo));
let mut attach = Attach::new(gl.clone(), vao, vbo, Some(ebo), None, Some(1));
let (vbo, ebo) = self.generate_cube();
attach.bind_data(gl, &vbo, Some(&ebo), ebo.len() as i32, glow::STATIC_DRAW);
attach
}
}
fn generate_cube(&self) -> (Vec<f32>, Vec<u32>) {
let mut vertices = vec![];
let mut indices = vec![];
let mut add_vertex = |x: f32, y: f32, z: f32| {
vertices.push(x);
vertices.push(y);
vertices.push(z);
};
let mut add_indices = |a: u32, b: u32, c: u32| {
indices.push(a);
indices.push(b);
indices.push(c);
};
let mut add_face = |a: u32, b: u32, c: u32, d: u32| {
add_indices(a, b, c);
add_indices(a, c, d);
};
let r = self.earth_radius;
// Front
add_vertex(-r, -r, r);
add_vertex(r, -r, r);
add_vertex(r, r, r);
add_vertex(-r, r, r);
add_face(0, 1, 2, 3);
// Back
add_vertex(-r, -r, -r);
add_vertex(-r, r, -r);
add_vertex(r, r, -r);
add_vertex(r, -r, -r);
add_face(4, 5, 6, 7);
// Top
add_vertex(-r, r, -r);
add_vertex(-r, r, r);
add_vertex(r, r, r);
add_vertex(r, r, -r);
add_face(8, 9, 10, 11);
// Bottom
add_vertex(-r, -r, -r);
add_vertex(r, -r, -r);
add_vertex(r, -r, r);
add_vertex(-r, -r, r);
add_face(12, 13, 14, 15);
// Right
add_vertex(r, -r, -r);
add_vertex(r, r, -r);
add_vertex(r, r, r);
add_vertex(r, -r, r);
add_face(16, 17, 18, 19);
// Left
add_vertex(-r, -r, -r);
add_vertex(-r, -r, r);
add_vertex(-r, r, r);
add_vertex(-r, r, -r);
add_face(20, 21, 22, 23);
(vertices, indices)
}
}
impl Default for EarthConfig {
fn default() -> Self {
Self {
earth_radius: 3.0,
sun_location: [10.0, 0.0, 0.0],
}
}
}
impl Graphics for Earth {
const id: &'static str = "Earth";
type Config = EarthConfig;
fn compile(&mut self, gl: &glow::Context) -> Result<()> {
self.program.compile(gl)
}
fn destroy(&mut self, gl: &glow::Context) -> Result<()> {
self.program.destroy(gl);
Ok(())
}
fn draw(&self, gl: &glow::Context, count: i32) -> Result<()> {
self.attach.bind_self(gl, &self.program);
unsafe {
// gl.draw_arrays(glow::POINTS, 0, count);
gl.draw_elements(glow::TRIANGLES, self.attach.len, glow::UNSIGNED_INT, 0);
}
Ok(())
}
fn mount(&self, gl: &glow::Context) -> Result<()> {
unsafe {
gl.use_program(self.program.native_program);
}
Ok(())
}
fn unmount(&self, gl: &glow::Context) -> Result<()> {
unsafe {
gl.use_program(None);
}
Ok(())
}
fn program_ref(&self) -> &Program {
&self.program
}
fn program_mut(&mut self) -> &mut Program {
&mut self.program
}
fn set_config(&mut self, gl: &glow::Context, config: &Self::Config) -> Result<()> {
Ok(())
}
}

View File

@ -0,0 +1 @@
pub mod earth;

View File

@ -21,7 +21,7 @@ use glow::HasContext;
use super::{
layout_type::{self, ViewPort},
ModuleRefs,
Module, ModuleRefs,
};
use super::{ModulePackage, Programs};
use crate::{font_manager::FontManager, graphics::font::Text};
@ -29,19 +29,29 @@ use crate::{font_manager::FontManager, graphics::font::Text};
pub struct App {
gl: GL,
pub context: Context,
pub init_modules: Option<ModulePackage>,
}
impl App {
pub fn new(gl: GL, helper: Helper) -> Result<Self> {
let programs = Programs::new(gl.clone()).unwrap();
let context = Context::new(gl.clone(), helper, programs);
Ok(Self { gl, context })
Ok(Self {
gl,
context,
init_modules: None,
})
}
pub fn prepare(&mut self) {
pub fn prepare(&mut self, setting: &radarg_core::config::Setting) -> Result<()> {
if let Err(e) = self.context.programs.prepare() {
error!("prepare failed: {:?}", e);
}
let init_modules = self.init_modules(setting)?;
self.init_modules = Some(init_modules);
Ok(())
}
pub fn render<'a>(
@ -50,6 +60,11 @@ impl App {
operation: &Operation<PlaneTrans>,
viewport: &ViewPort,
) {
if let Some(module) = self.init_modules.as_mut() {
let programs = &mut self.context.programs;
programs.draw_modules(module, operation, viewport);
}
for module in modules {
let mut module = module.borrow_mut();
self.program()
@ -64,6 +79,11 @@ impl App {
&mut self.context.programs
}
fn init_modules(&mut self, setting: &radarg_core::config::Setting) -> Result<ModulePackage> {
let earth_module = self.program().earth();
earth_module.load_data(&(), setting).map(|v| v.into())
}
pub fn supported_modules<'a>(
&mut self,
data: &'a Value<Data>,

View File

@ -3,10 +3,7 @@ use femtovg::renderer::OpenGl;
use femtovg::Canvas;
use glow::HasContext;
use layout_type::ViewPort;
use modules::{
GeoQuadMeshModuleConfigComponent, GeoQuadMeshModuleRef, GeoQuadMeshPackage,
PPIModuleConfigComponent, PPIModuleRef,
};
use modules::*;
use radarg_core::{datapool::Value, radarg_data::Data};
use paste::paste;
@ -25,15 +22,20 @@ use crate::{
errors::*,
font_manager::FontManager,
graphics::{
collections::agg_fast_path::AggFastPath, font::Text, geoquadmesh::GeoQuadMesh, ppi::PPI,
transforms::plane::PlaneTrans, AttaWithProgram, AttachWithIO, Graphics,
collections::agg_fast_path::AggFastPath,
font::Text,
geoquadmesh::GeoQuadMesh,
planet::earth::{self, Earth},
ppi::PPI,
transforms::plane::PlaneTrans,
AttaWithProgram, AttachWithIO, Graphics,
},
ui::{operation::Operation, typ::LayoutAttach},
utils::resources::GL,
};
pub use app::{App, Context};
pub use modules::{GeoQuadMeshModule, Module, ModuleCursor, PPIModule, PPIPackage};
pub use modules::{EarthModule, GeoQuadMeshModule, Module, ModuleCursor, PPIModule, PPIPackage};
use std::sync::atomic::AtomicUsize;
use std::{cell::RefCell, collections::HashMap};
use std::{rc::Rc, sync::Arc};
@ -50,15 +52,15 @@ pub enum SideBarInputMsg {
}
macro_rules! program_impl {
($({$name:ident | $name_ref:ident, $module: ty | $module_ref: ty, ($($m:ident),+)}),+) => {
($({$name:ident | $name_ref:ident, $module: ty | $module_ref: ty, ($($m:ident),*)}),+) => {
impl Programs {
$(
pub fn $name(&mut self) -> $module {
<$module>::new(&self.gl, $(&mut self.$m),+)
<$module>::new(&self.gl, $(&mut self.$m),*)
}
pub fn $name_ref(&self) -> $module_ref {
<$module_ref>::new(&self.gl, $(&self.$m),+)
<$module_ref>::new(&self.gl, $(&self.$m),*)
}
)+
@ -74,17 +76,20 @@ pub struct Programs {
_text: Text,
_line: AggFastPath,
_geo_quad_mesh: GeoQuadMesh,
_earth: Earth,
}
impl Programs {
fn new(gl: GL) -> Result<Self> {
let font_manager = FontManager::new()?;
let earth = Earth::new(&gl)?;
Ok(Self {
gl: gl.clone(),
_ppi: PPI::new()?,
_text: Text::new(gl, font_manager)?,
_line: AggFastPath::new()?,
_geo_quad_mesh: GeoQuadMesh::new()?,
_earth: earth,
})
}
@ -93,6 +98,7 @@ impl Programs {
self._line.program().compile(&self.gl)?;
self._text.program_mut().compile(&self.gl)?;
self._geo_quad_mesh.program().compile(&self.gl)?;
self._earth.program_mut().compile(&self.gl)?;
Ok(())
}
@ -101,6 +107,7 @@ impl Programs {
self._text.destroy(&self.gl)?;
self._line.destroy(&self.gl)?;
self._geo_quad_mesh.destroy(&self.gl)?;
self._earth.destroy(&self.gl)?;
Ok(())
}
}
@ -162,7 +169,7 @@ macro_rules! impl_module_package {
pub enum ComponentBuilders {
$(
$b(ComponentBuilder<$c>, Rc<RefCell<<$t as ModuleCursor>::Config>>, Box<dyn Fn(<$t as ModuleCursor>::ComponentOutput) -> SideBarInputMsg>),
$b(ComponentBuilder<$c>, <<$t as ModuleCursor>::Component as relm4::Component>::Init, Box<dyn Fn(<$t as ModuleCursor>::ComponentOutput) -> SideBarInputMsg>),
)+
}
@ -282,12 +289,14 @@ macro_rules! impl_module_package {
program_impl!(
{ppi | ppi_ref, PPIModule | PPIModuleRef, (_ppi, _text, _line)},
{geo_quad_mesh | geo_quad_mesh_ref, GeoQuadMeshModule | GeoQuadMeshModuleRef, (_geo_quad_mesh,_text, _line)}
{geo_quad_mesh | geo_quad_mesh_ref, GeoQuadMeshModule | GeoQuadMeshModuleRef, (_geo_quad_mesh,_text, _line)},
{earth | earth_ref, EarthModule | EarthModuleRef, (_earth)}
);
impl_module_package!(
{ppi|ppi_ref,PPIPackage => PPI | PPIModule | PPIModuleRef | PPIModuleConfigComponent},
{geo_quad_mesh|geo_quad_mesh_ref,GeoQuadMeshPackage => GeoQuadMesh | GeoQuadMeshModule | GeoQuadMeshModuleRef | GeoQuadMeshModuleConfigComponent}
{geo_quad_mesh|geo_quad_mesh_ref,GeoQuadMeshPackage => GeoQuadMesh | GeoQuadMeshModule | GeoQuadMeshModuleRef | GeoQuadMeshModuleConfigComponent},
{earth|earth_ref, EarthModulePackage => Earth| EarthModule | EarthModuleRef | EarthModuleConfigComponent}
);
impl ModulePackage {

174
gi/src/pg/modules/earth.rs Normal file
View File

@ -0,0 +1,174 @@
use geo_macros::{module_ref_impl, ModuleRef};
use glow::HasContext;
use relm4::{
adw::{self, prelude::*},
gtk::{self, prelude::*},
view, ComponentParts, SimpleComponent,
};
use std::{cell::RefCell, rc::Rc, sync::Arc};
use crate::{
graphics::{
planet::earth::{Earth, EarthConfig},
transforms::plane::PlaneTrans,
Graphics,
},
GL,
};
use super::{Module, ModuleCursor};
#[derive(ModuleRef)]
pub struct EarthModule<'b, 'gl: 'b> {
gl: &'gl GL,
earth: &'b mut Earth,
}
impl<'b, 'gl: 'b> EarthModule<'b, 'gl> {
pub fn name(&self) -> &'static str {
"Earth"
}
pub fn supported(&self, data: &Arc<radarg_core::Data>) -> bool {
false
}
}
impl<'b, 'gl: 'b> EarthModuleRef<'b, 'gl> {
pub fn name(&self) -> &'static str {
"Earth"
}
pub fn supported(&self, data: &Arc<radarg_core::Data>) -> bool {
false
}
}
impl<'b, 'gl: 'b> EarthModule<'b, 'gl> {
pub fn new(gl: &'gl GL, earth: &'b mut Earth) -> Self {
Self { gl, earth }
}
}
impl<'b, 'gl: 'b> EarthModuleRef<'b, 'gl> {
pub fn new(gl: &'gl GL, earth: &'b Earth) -> Self {
Self { gl, earth }
}
}
pub struct EarthModulePackage {}
impl<'b, 'gl: 'b> Module for EarthModule<'b, 'gl> {
type Cursor = EarthModulePackage;
type Data = ();
type Operation = PlaneTrans;
const NAME: &'static str = "Earth";
fn load_data<'dt>(
&self,
data: &Self::Data,
setting: &radarg_core::config::Setting,
) -> super::Result<Self::Cursor> {
Ok(EarthModulePackage {})
}
fn render(
&mut self,
cursor: &mut Self::Cursor,
operation: &crate::ui::operation::Operation<Self::Operation>,
viewport: &crate::pg::layout_type::ViewPort,
) -> super::Result<()> {
self.earth.mount(&self.gl)?;
self.earth.set_config(&self.gl, &EarthConfig::default())?;
operation.attach_with_program(&self.gl, self.earth.program_mut());
// Camera Info
let camera_loc = operation.camera().get_position();
// focal length
let focal_length = operation.projection().fov();
let focal_length = 1.0 / ((focal_length / 2.0).to_radians().tan());
// Viewport Info
let resolution = viewport.size();
let znear = operation.projection().z_near();
let zfar = operation.projection().z_far();
unsafe {
let earth_program = self.earth.program_mut();
let cloc = earth_program.get_uniform_location(&self.gl, "camera_location");
let foc = earth_program.get_uniform_location(&self.gl, "focal_length");
let res = earth_program.get_uniform_location(&self.gl, "resolution");
let projection_params =
earth_program.get_uniform_location(&self.gl, "projection_params");
self.gl
.uniform_3_f32(cloc.as_ref(), camera_loc.x, camera_loc.y, camera_loc.z);
self.gl.uniform_1_f32(foc.as_ref(), focal_length);
self.gl.uniform_2_f32_slice(res.as_ref(), &resolution);
self.gl
.uniform_2_f32(projection_params.as_ref(), znear, zfar);
}
self.earth.draw(&self.gl, 1);
Ok(())
}
fn supported(&self, data: &radarg_core::Data) -> bool {
false
}
}
impl ModuleCursor for EarthModulePackage {
type Module<'rf, 'gl: 'rf> = EarthModule<'rf, 'gl>;
type Config = ();
type Data = ();
type Component = EarthModuleConfigComponent;
type ComponentOutput = ();
fn set_config<F>(&mut self, f: F)
where
F: FnOnce(&mut Self::Config),
{
}
fn component_config(&self) -> () {
()
}
fn component_sender(&self) -> Box<dyn Fn(Self::ComponentOutput) -> crate::pg::SideBarInputMsg> {
todo!()
}
}
pub struct EarthModuleConfigComponent {}
#[relm4::component(pub)]
impl SimpleComponent for EarthModuleConfigComponent {
type Widgets = EarthModuleConfigComponentWidgets;
type Init = ();
type Input = ();
type Output = ();
view! {
adw::PreferencesPage {
}
}
fn init(
init: Self::Init,
root: Self::Root,
sender: relm4::ComponentSender<Self>,
) -> relm4::ComponentParts<Self> {
let model = EarthModuleConfigComponent {};
let widgets = view_output!();
ComponentParts { model, widgets }
}
fn update(&mut self, message: Self::Input, sender: relm4::ComponentSender<Self>) {}
}

View File

@ -169,7 +169,7 @@ impl<'b, 'a: 'b> GeoQuadMeshModule<'b, 'a> {
impl<'b, 'a: 'b> Module for GeoQuadMeshModule<'b, 'a> {
type Cursor = GeoQuadMeshPackage;
type Data = RadarGridData;
type Data = Arc<RadarGridData>;
type Operation = PlaneTrans;
const NAME: &'static str = "GeoQuadMesh";
@ -194,7 +194,7 @@ impl<'b, 'a: 'b> Module for GeoQuadMeshModule<'b, 'a> {
.set_config(&self.gl, &config.to_quad_config())?;
// Quad Draw
quad_attach.bind_self(&self.gl,self.geo_quad_mesh_program.program_ref());
quad_attach.bind_self(&self.gl, self.geo_quad_mesh_program.program_ref());
// Bind the texture
#[cfg(target_os = "macos")]
@ -214,7 +214,7 @@ impl<'b, 'a: 'b> Module for GeoQuadMeshModule<'b, 'a> {
Ok(())
}
fn load_data<'dt>(&self, data: &Arc<Self::Data>, setting: &Setting) -> Result<Self::Cursor> {
fn load_data<'dt>(&self, data: &Self::Data, setting: &Setting) -> Result<Self::Cursor> {
// Init the memory
let (vao, vbo, ebo, texs) = self.geo_quad_mesh_program.init(&self.gl);
let mut quad_attach = Attach::new(self.gl.clone(), vao, vbo, ebo, texs, None);
@ -530,7 +530,7 @@ impl ModuleCursor for GeoQuadMeshPackage {
f(&mut config);
}
fn component_config(&self) -> Rc<RefCell<Self::Config>> {
fn component_config(&self) -> <Self::Component as relm4::Component>::Init {
self.ppi_config.clone()
}

View File

@ -14,6 +14,7 @@ use femtovg::{renderer::OpenGl, Canvas};
use glow::{HasContext, NativeBuffer, NativeTexture, NativeVertexArray};
use radarg_core::{config::Setting, Data};
use std::{cell::RefCell, path::Component, rc::Rc, sync::Arc};
mod earth;
mod geoquadmesh;
mod ppi;
use crate::errors::*;
@ -25,10 +26,19 @@ pub use ppi::{
PPIModule, PPIModuleConfigComponent, PPIModuleConfigComponentWidgets, PPIModuleRef, PPIPackage,
};
pub use earth::{
EarthModule, EarthModuleConfigComponent, EarthModuleConfigComponentWidgets, EarthModulePackage,
EarthModuleRef,
};
use relm4::Component as RComponent;
use super::{layout_type::ViewPort, SideBarInputMsg};
macro_rules! module_ref {
($name:ident) => {};
}
#[derive(Clone, Debug)]
pub(crate) struct Attach {
gl: GL,
@ -65,20 +75,19 @@ impl Attach {
}
}
fn bind_self(&self,gl: &glow::Context, program: &Program) {
pub fn bind_self(&self, gl: &glow::Context, program: &Program) {
#[cfg(target_os = "macos")]
{
self.vao.bind(gl,glow::VERTEX_ARRAY);
self.vbo.bind(gl,glow::ARRAY_BUFFER);
self.vao.bind(gl, glow::VERTEX_ARRAY);
self.vbo.bind(gl, glow::ARRAY_BUFFER);
if let Some(ebo) = self.ebo.as_ref() {
ebo.bind(gl,glow::ELEMENT_ARRAY_BUFFER);
ebo.bind(gl, glow::ELEMENT_ARRAY_BUFFER);
}
}
#[cfg(not(target_os = "macos"))]
unsafe {
gl.bind_vertex_array(Some(self.vao.native()));
}
}
fn unbind_self(&self) {
@ -89,21 +98,26 @@ impl Attach {
}
}
pub fn bind_data(&mut self,gl:&glow::Context, vbo: &Vec<f32>, ebo: Option<&Vec<u32>>, len: i32, usage: u32) {
pub fn bind_data(
&mut self,
gl: &glow::Context,
vbo: &Vec<f32>,
ebo: Option<&Vec<u32>>,
len: i32,
usage: u32,
) {
use bytemuck::cast_slice;
#[cfg(target_os = "macos")]
{
self.vbo.bind(gl,glow::ARRAY_BUFFER);
self.vbo.bind(gl, glow::ARRAY_BUFFER);
unsafe {
gl
.buffer_data_u8_slice(glow::ARRAY_BUFFER, cast_slice(&vbo), usage);
gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, cast_slice(&vbo), usage);
if let Some(ebo) = ebo {
gl.bind_buffer(
glow::ELEMENT_ARRAY_BUFFER,
Some(self.ebo.as_ref().unwrap().native()),
);
gl
.buffer_data_u8_slice(glow::ELEMENT_ARRAY_BUFFER, cast_slice(&ebo), usage);
gl.buffer_data_u8_slice(glow::ELEMENT_ARRAY_BUFFER, cast_slice(&ebo), usage);
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
}
@ -114,13 +128,17 @@ impl Attach {
#[cfg(not(target_os = "macos"))]
{
unsafe {
gl.named_buffer_data_u8_slice(self.vbo.native(), cast_slice(&vbo), usage);
if let Some(ebo) = ebo {
gl.named_buffer_data_u8_slice(self.ebo.as_ref().unwrap().native(), cast_slice(ebo), usage);
}
gl.named_buffer_data_u8_slice(self.vbo.native(), cast_slice(&vbo), usage);
if let Some(ebo) = ebo {
gl.named_buffer_data_u8_slice(
self.ebo.as_ref().unwrap().native(),
cast_slice(ebo),
usage,
);
}
}
}
self.len = len;
}
@ -143,7 +161,7 @@ pub trait Module: Sized {
viewport: &ViewPort,
) -> Result<()>;
fn load_data<'dt>(&self, data: &Arc<Self::Data>, setting: &Setting) -> Result<Self::Cursor>;
fn load_data<'dt>(&self, data: &Self::Data, setting: &Setting) -> Result<Self::Cursor>;
fn supported(&self, data: &Data) -> bool;
}
@ -160,7 +178,7 @@ pub trait ModuleCursor {
where
F: FnOnce(&mut Self::Config);
fn component_config(&self) -> Rc<RefCell<Self::Config>>;
fn component_config(&self) -> <Self::Component as RComponent>::Init;
fn component_sender(&self) -> Box<dyn Fn(Self::ComponentOutput) -> SideBarInputMsg>;
}

View File

@ -282,7 +282,7 @@ impl<'b, 'a: 'b> PPIModule<'b, 'a> {
impl<'b, 'a: 'b> Module for PPIModule<'b, 'a> {
type Cursor = PPIPackage;
type Data = RadarGridData;
type Data = Arc<RadarGridData>;
type Operation = PlaneTrans;
const NAME: &'static str = "PPI";
@ -314,10 +314,10 @@ impl<'b, 'a: 'b> Module for PPIModule<'b, 'a> {
}
// PPI Draw
ppi_attach.bind_self(&self.gl,&self.ppi_program.program_ref());
ppi_attach.bind_self(&self.gl, &self.ppi_program.program_ref());
self.ppi_program.draw(&self.gl, ppi_attach.len())?;
#[cfg(target_os = "macos")]
ppi_attach.unbind_self();
@ -346,7 +346,7 @@ impl<'b, 'a: 'b> Module for PPIModule<'b, 'a> {
// PPI Tick Draw
let attach = &mut cursor.line_attach;
attach.bind_self(&self.gl,self.line_program.program_ref());
attach.bind_self(&self.gl, self.line_program.program_ref());
self.line_program.draw(&self.gl, attach.len())?;
attach.unbind_self();
@ -361,7 +361,7 @@ impl<'b, 'a: 'b> Module for PPIModule<'b, 'a> {
self.text_program
.set_config(&self.gl, &config.to_font_config());
tick_attach.bind_self(&self.gl,self.text_program.program_ref());
tick_attach.bind_self(&self.gl, self.text_program.program_ref());
self.text_program.draw(&self.gl, tick_attach.len())?;
tick_attach.unbind_self();
@ -370,7 +370,7 @@ impl<'b, 'a: 'b> Module for PPIModule<'b, 'a> {
Ok(())
}
fn load_data<'dt>(&self, data: &Arc<Self::Data>, setting: &Setting) -> Result<Self::Cursor> {
fn load_data<'dt>(&self, data: &Self::Data, setting: &Setting) -> Result<Self::Cursor> {
// Init the memory
let (vao, vbo, ebo, texs) = self.ppi_program.init(&self.gl);
let mut ppi_attach = Attach::new(self.gl.clone(), vao, vbo, ebo, texs, None);

151
gi/src/shaders/earth.rs Normal file
View File

@ -0,0 +1,151 @@
use super::trackball::Trackball;
use super::CodePiece;
use crate::impl_code_piece;
use glsl::syntax::ShaderStage;
use glsl::syntax::TranslationUnit;
use glsl::transpiler::glsl::show_translation_unit;
use glsl_quasiquote::glsl;
pub struct EarthVertex(pub ShaderStage);
pub struct EarthFragment(pub ShaderStage);
impl EarthVertex {
pub fn new() -> Self {
let mut trackball = Trackball::new().0;
let raw = glsl! {
layout(location = 0) in vec3 in_position;
void main() {
gl_Position = transform(vec4(in_position, 1.0));
}
};
trackball.extend(raw);
Self(trackball)
}
}
impl EarthFragment {
pub fn new() -> Self {
let raw = glsl! {
uniform vec3 camera_location;
uniform float focal_length;
uniform vec2 resolution;
uniform mat4 trackball_view;
uniform vec2 projection_params;
uniform vec3 sun_location;
uniform float ER;
out vec4 FragColor;
float sdSphere(vec3 p, float s) {
return length(p) - s;
}
vec2 iBox(in vec3 ro, in vec3 rd, in vec3 rad ){
vec3 m = 1.0/rd;
vec3 n = m*ro;
vec3 k = abs(m)*rad;
vec3 t1 = -n - k;
vec3 t2 = -n + k;
return vec2( max( max( t1.x, t1.y ), t1.z ),min( min( t2.x, t2.y ), t2.z ) );
}
vec3 calcNormal( in vec3 pos ) {
vec2 e = vec2(1.0,-1.0)*0.5773*0.0005;
return normalize( e.xyy*sdSphere( pos + e.xyy, ER ) +
e.yyx*sdSphere( pos + e.yyx ,ER) +
e.yxy*sdSphere( pos + e.yxy ,ER) +
e.xxx*sdSphere( pos + e.xxx ,ER) );
}
float raycast(in vec3 ro, in vec3 rd) {
float res = -1.0;
vec2 tb = iBox(ro, rd, vec3(ER, ER, ER));
float tmin = projection_params.x;
float tmax = projection_params.y;
if(tb.x < tb.y && tb.y > 0.0 && tb.x < tmax) {
tmin = max(tb.x, tmin);
tmax = min(tb.y, tmax);
float t = tmin;
int i = 0;
while ( i < 70 && t < tmax) {
float h = sdSphere(ro + rd * t, ER);
if(abs(h) < (0.0001 * t)) {
res = t;
break;
}
t += h;
i += 1;
}
}
return res;
}
void main() {
vec2 p = (2.0 * gl_FragCoord.xy - resolution) / resolution.y;
vec3 ro = camera_location;
mat3 ca = transpose(mat3(trackball_view));
vec3 rd = ca * normalize(vec3(p, focal_length));
float t = raycast(ro, rd);
float ks = 1.0;
vec3 lin = vec3(0.0);
if (t < 0.0) {
discard;
}
vec3 col = vec3(1.0, 1.0, 1.0);
vec3 pos = ro + t * rd;
vec3 nor = calcNormal(pos);
// vec3 ref = reflect(rd, nor);
// If we hit the sphere
vec3 lig = normalize(sun_location);
vec3 hal = normalize(lig - rd);
float dif = clamp( dot( nor, lig ), 0.0, 1.0 );
float spe = pow( clamp( dot( nor, hal ), 0.0, 1.0 ),16.0);
spe *= dif;
spe *= 0.04+0.96*pow(clamp(1.0-dot(hal,lig),0.0,1.0),5.0);
lin += col*2.20*dif*vec3(1.30,1.00,0.70);
lin += 5.00*spe*vec3(1.30,1.00,0.70)*ks;
col = mix( col, vec3(0.7,0.7,0.9), 1.0-exp( -0.0001*t*t*t ) );
FragColor = vec4(clamp( col, 0.0, 1.0 ),1.0);
}
};
Self(raw)
}
}
impl_code_piece!(EarthVertex, 0);
impl_code_piece!(EarthFragment, 0);

View File

@ -86,7 +86,7 @@ impl GeoQuadMeshFragment {
float ty = float((loc % (tex_height * tex_width) / tex_width));
float tx = float(loc % tex_width);
vec3 tex_coord = vec3(tx / tex_shape.z, ty / tex_shape.y, tz / tex_shape.x);
vec3 tex_coord = vec3(tx / (tex_shape.z - 1.0), ty / (tex_shape.y - 1.0), tz / (tex_shape.x - 1.0));
vec4 _value = texture(data_tex, tex_coord);

View File

@ -1,5 +1,6 @@
pub mod agg_path;
pub mod colormap;
pub mod earth;
pub mod font;
pub mod geoquadmesh;
pub mod math;
@ -7,6 +8,7 @@ pub mod polar;
pub mod ppi;
pub mod proj;
pub mod trackball;
use glsl::{
syntax::{ShaderStage, TranslationUnit},
transpiler::glsl::{show_struct, show_translation_unit},

View File

@ -38,6 +38,14 @@ impl<T: AttachWithIO + AttaWithProgram> Operation<T> {
self.need_update = true;
}
pub fn camera(&self) -> &Camera {
&self.camera
}
pub fn projection(&self) -> &Projection {
&self.projection
}
pub fn set_projection(&mut self, f: impl Fn(&mut Projection)) {
f(&mut self.projection);
self.need_update = true;

View File

@ -1,3 +1,4 @@
use super::file_list::file_list::{FileListInputMsg, FileListModel, FileListOutputMsg};
use super::sidebar::SideBarOutputMsg;
use super::{
control_panel::{ControlPanelInputMsg, ControlPanelModel},
@ -42,6 +43,7 @@ pub static FILE_PATH_ROOT: Lazy<Mutex<PathBuf>> = Lazy::new(|| Mutex::new(PathBu
pub type ElementKey = String;
pub type Share<T> = Rc<RefCell<T>>;
pub type ThreadShare<T> = Arc<Mutex<T>>;
#[derive(Debug)]
pub enum LayerMsg {
@ -58,6 +60,12 @@ pub enum FileIOType {
Save,
}
#[derive(Debug)]
pub enum AlertType {
Loading,
Error { body: String },
}
#[derive(Debug)]
pub enum AppMsg {
Refresh,
@ -65,8 +73,9 @@ pub enum AppMsg {
IO { key: u32, pressed: bool },
OpenDialog { widget: Widget },
OpenFile(PathBuf),
OpenFolder(PathBuf),
CloseDialog,
OpenAlert { body: String, title: String },
OpenAlert(AlertType),
CloseAlert,
CloseRequest,
Close,
@ -77,6 +86,8 @@ pub enum AppMsg {
pub struct AppModel {
// Components
#[do_not_track]
file_list: Controller<FileListModel>,
#[do_not_track]
open_dialog: Controller<OpenDialog>,
// #[do_not_track]
// control: Controller<ControlPanelModel>,
@ -88,12 +99,12 @@ pub struct AppModel {
setting: Controller<SettingModel>,
// File Pool
#[do_not_track]
file_pool: Share<DataPool>,
file_pool: ThreadShare<DataPool>,
}
#[derive(Debug)]
pub enum AppCommand {
Test,
FileLoadResult(Result<Value<Data>, radarg_core::errors::DataError>),
}
#[relm4::component(pub)]
@ -155,7 +166,17 @@ impl Component for AppModel {
adw::ToastOverlay{
set_hexpand: true,
set_vexpand: true,
DynamicCol {
#[name="sidebar_controller"]
adw::OverlaySplitView {
set_show_sidebar: false,
set_collapsed: false,
#[wrap(Some)]
set_sidebar=model.file_list.widget(),
#[wrap(Some)]
set_content=&DynamicCol {
set_end_width: 300,
set_hexpand: true,
set_vexpand: true,
@ -170,6 +191,8 @@ impl Component for AppModel {
set_end_child=sidebar->gtk::Box{},
}
}
}
},
},
home_stack_page = view_stack.add_titled(&home_page, Some("home"), "Home") -> adw::ViewStackPage{
@ -214,7 +237,7 @@ impl Component for AppModel {
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let data_pool = DataPool::new(&PLUGIN_MANAGER, 10);
let data_pool = Rc::new(RefCell::new(data_pool));
let data_pool = Arc::new(Mutex::new(data_pool));
// SideBar Component
let sidebar = SideBarModel::builder()
@ -252,10 +275,7 @@ impl Component for AppModel {
AppMsg::CloseDialog
}
MonitorOutputMsg::Alert(content, title) => {
AppMsg::OpenAlert {
body: content,
title,
}
AppMsg::OpenAlert(AlertType::Error { body: content })
}
MonitorOutputMsg::Fc => {
AppMsg::Close
@ -290,23 +310,34 @@ impl Component for AppModel {
)
};
// Drop Target
let file_list = FileListModel::builder()
.launch(())
.forward(sender.input_sender(), |msg| match msg {
FileListOutputMsg::OpenFile(path) => AppMsg::OpenFile(path),
FileListOutputMsg::Error(e) => AppMsg::OpenAlert(AlertType::Error { body: e }),
});
// Drop Target -- File Target
let drop_target =
gtk::DropTarget::new(gtk::gio::File::static_type(), gtk::gdk::DragAction::COPY);
let drop_sender = sender.clone();
drop_target.connect_drop(move |_drop_target, value, _x, _y| {
if let Ok(file) = value.get::<gtk::gio::File>() {
if let Some(path) = file.path() {
drop_sender.input(AppMsg::OpenFile(path));
if path.is_file() {
drop_sender.input(AppMsg::OpenFile(path));
} else {
drop_sender.input(AppMsg::OpenFolder(path));
}
}
}
true
});
let model = AppModel {
file_list,
render,
setting,
open_dialog: dialog,
@ -396,36 +427,45 @@ impl Component for AppModel {
self.render.emit(MonitorInputMsg::KeyRelease(key));
}
}
AppMsg::OpenAlert { body, title } => {
widgets.alert_dialog.set_body(&body);
widgets.alert_dialog.set_title(&title);
widgets.alert_dialog.add_responses(&[("Close", "Close")]);
AppMsg::OpenAlert(alert) => match alert {
AlertType::Error { body } => {
widgets.alert_dialog.set_body(&body);
widgets.alert_dialog.set_title("Error");
widgets.alert_dialog.add_responses(&[("Close", "Close")]);
widgets
.alert_dialog
.set_response_appearance("Close", adw::ResponseAppearance::Destructive);
widgets
.alert_dialog
.set_response_appearance("Close", adw::ResponseAppearance::Destructive);
widgets.alert_dialog.present(Some(root));
widgets.alert_dialog.present(Some(root));
}
AlertType::Loading => {
widgets.dialog.set_child(Some(&widgets.popover_child));
widgets.dialog.present(Some(root));
// widgets.alert_dialog.set_body("Loading...");
// widgets
// .alert_dialog
// .set_extra_child(Some(&widgets.popover_child));
// widgets.alert_dialog.set_title("Loading");
// widgets.alert_dialog.present(Some(root));
}
},
AppMsg::CloseAlert => {
widgets.alert_dialog.close();
}
AppMsg::OpenFile(path) => {
let mut datapool = self.file_pool.borrow_mut();
match datapool.get_or_load(path) {
Ok(data) => {
info!("data: {:?}", data);
_sender.input(AppMsg::Packages { clear: true });
_sender.input(AppMsg::FileIO {
typ: FileIOType::Open(data),
});
}
Err(e) => {
error!("Failed to load data, cause: {:?}", e);
_sender.input(AppMsg::OpenAlert {
body: format!("Failed to load data, cause: {:?}", e),
title: "Error".to_string(),
});
}
}
_sender.input(AppMsg::OpenAlert(AlertType::Loading));
let datapool = self.file_pool.clone();
_sender.spawn_oneshot_command(move || {
let mut datapool = datapool.lock().unwrap();
AppCommand::FileLoadResult(datapool.get_or_load(path))
});
}
AppMsg::OpenFolder(folder) => {
self.file_list.emit(FileListInputMsg::ChooseFolder(folder));
widgets.sidebar_controller.set_show_sidebar(true);
}
AppMsg::FileIO {
typ: FileIOType::Open(data),
@ -448,11 +488,20 @@ impl Component for AppModel {
root: &Self::Root,
) {
match message {
_ => {
println!("test");
AppCommand::FileLoadResult(result) => {
match result {
Ok(data) => {
self.render.emit(MonitorInputMsg::PushData(data));
}
Err(e) => {
sender.input(AppMsg::OpenAlert(AlertType::Error {
body: e.to_string(),
}));
}
};
sender.input(AppMsg::CloseAlert);
sender.input(AppMsg::CloseDialog);
}
}
}
}
impl AppModel {}

View File

@ -0,0 +1,136 @@
use crate::actions::*;
use abi_stable::type_level::trait_marker::Hash;
use chrono::{DateTime, Utc};
use gi::pg::SideBarInputMsg;
use gi::pg::{Components, ModulePackage};
use glib_macros::clone;
use gtk::prelude::WidgetExt;
use gtk::prelude::*;
use gtk::{glib, Widget};
use relm4::actions::{AccelsPlus, RelmAction};
use relm4::RelmRemoveAllExt;
use relm4::{
adw::{self, prelude::*},
binding::{Binding, U8Binding},
factory::{DynamicIndex, FactoryComponent, FactorySender, FactoryVecDeque},
gtk::gio,
prelude::*,
typed_view::{
column::TypedColumnView,
list::{RelmListItem, TypedListView},
},
view, RelmObjectExt,
};
use std::path::PathBuf;
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use super::list::FileListItem;
use crate::components::app::AppMsg;
use crate::{predefined::color_mapper::BoundaryNorm, widgets::render::Layer};
pub struct FileListModel {
list: TypedListView<FileListItem, gtk::SingleSelection>,
}
#[derive(Debug)]
pub enum FileListOutputMsg {
OpenFile(PathBuf),
Error(String),
}
#[derive(Debug)]
pub enum FileListInputMsg {
ChooseFolder(PathBuf),
}
#[relm4::component(pub)]
impl Component for FileListModel {
type Init = ();
type Input = FileListInputMsg;
type Output = FileListOutputMsg;
type CommandOutput = ();
view! {
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_hexpand: true,
gtk::Label {
set_text: "Files",
set_margin_top: 10,
set_margin_bottom: 10,
set_margin_start: 10,
set_margin_end: 10,
set_halign: gtk::Align::Start,
set_valign: gtk::Align::Center,
set_widget_name: "title",
},
gtk::ScrolledWindow {
set_vexpand: true,
#[local_ref]
list_widget -> gtk::ListView {
set_margin_horizontal: 10,
set_show_separators: true,
set_single_click_activate: false,
}
}
}
}
fn init(
init: Self::Init,
root: Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let mut list = TypedListView::with_sorting();
let mut model = Self { list };
let list_widget = &model.list.view;
let widgets = view_output!();
ComponentParts { model, widgets }
}
fn update_with_view(
&mut self,
widgets: &mut Self::Widgets,
message: Self::Input,
sender: ComponentSender<Self>,
root: &Self::Root,
) {
match message {
FileListInputMsg::ChooseFolder(folder) => {
self.list.clear();
match std::fs::read_dir(folder) {
Ok(folder) => {
for entry in folder {
match entry {
Ok(entry) => {
let path = entry.path();
if path.is_file() {
self.list.append(FileListItem::new(path));
}
}
Err(e) => {
sender.output(FileListOutputMsg::Error(format!(
"Error reading folder: {}",
e
)));
}
}
}
}
Err(e) => {
sender.output(FileListOutputMsg::Error(format!(
"Error reading folder: {}",
e
)));
}
};
}
}
}
}

View File

@ -0,0 +1,50 @@
use std::path::PathBuf;
use cairo::Path;
use gtk::prelude::*;
use relm4::{
binding::{Binding, U8Binding},
prelude::*,
typed_view::list::{RelmListItem, TypedListView},
RelmObjectExt,
};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(super) struct FileListItem {
path: PathBuf,
}
impl FileListItem {
pub fn new<P: Into<PathBuf>>(path: P) -> Self {
Self { path: path.into() }
}
}
pub(super) struct Widgets {
label: gtk::Button,
}
impl RelmListItem for FileListItem {
type Root = gtk::Button;
type Widgets = Widgets;
fn setup(_item: &gtk::ListItem) -> (gtk::Button, Widgets) {
relm4::view! {
label=gtk::Button {
set_hexpand: true,
},
}
let widgets = Widgets {
label: label.clone(),
};
(label, widgets)
}
fn bind(&mut self, widgets: &mut Self::Widgets, _root: &mut Self::Root) {
let Widgets { label } = widgets;
label.set_label(self.path.file_name().unwrap().to_str().unwrap());
}
}

View File

@ -0,0 +1,2 @@
pub mod file_list;
mod list;

View File

@ -1,5 +1,6 @@
pub mod app;
mod control_panel;
pub mod file_list;
pub mod monitor;
mod setting;
pub mod sidebar;

View File

@ -5,6 +5,7 @@ use radarg_core::Data;
use std::{cell::RefCell, fmt::Debug, rc::Rc, sync::Arc};
pub enum MonitorInputMsg {
Start,
PushData(Value<Data>),
Draw(&'static str, Arc<Data>),
Prepare(Vec<Arc<Data>>),
@ -18,6 +19,7 @@ pub enum MonitorInputMsg {
impl Debug for MonitorInputMsg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MonitorInputMsg::Start => write!(f, "MonitorInputMsg::Start"),
MonitorInputMsg::QueueDraw => write!(f, "MonitorInputMsg::QueueDraw"),
MonitorInputMsg::Draw(module, data) => {
write!(f, "MonitorInputMsg::Draw({:?})", data)

View File

@ -9,7 +9,7 @@ use crate::{
};
use geo::k_nearest_concave_hull;
use gi::graphics::transforms::plane::PlaneTrans;
use gi::pg::{GeoQuadMeshModule, Module, ModulePackage, PPIModule};
use gi::pg::{EarthModule, GeoQuadMeshModule, Module, ModulePackage, PPIModule};
use gi::ui::operation::Operation;
use gtk::glib::clone;
@ -37,6 +37,7 @@ macro_rules! impl_draw {
match $module_name {
$(
<$module>::NAME => {
// let data = $data.as_ref().map(|v| &*v).unwrap_or(());
match gi.program().$method().load_data(data.try_into().unwrap(), &SETTING) {
Ok(package) => {
let package = Rc::new(RefCell::new(package.into()));
@ -106,11 +107,6 @@ impl Component for MonitorModel {
set_child = &Render{
#[track = "model.changed(MonitorModel::render_cfg())"]
set_cfg: model.render_cfg,
connect_render_status_notify[sender] => move |r| {
},
connect_scale_notify[sender] => move |r| {
let scale = r.scale();
},
#[track = "model.changed(MonitorModel::module_packages())"]
draw: &model.module_packages.iter().map(|m| m.1.clone()).collect(),
@ -136,6 +132,9 @@ impl Component for MonitorModel {
) {
self.reset();
match message {
MonitorInputMsg::Start => {
// Init Earth
}
MonitorInputMsg::PushData(data) => {
widgets.renderer.get_gi(|gi| {
let supported_modules = gi.supported_modules(&data);
@ -225,9 +224,15 @@ impl Component for MonitorModel {
sender.output(MonitorOutputMsg::DialogClose);
}),
MonitorInputMsg::Draw(module, ref data) => {
impl_draw!(self, module, widgets, data, sender, { PPIModule | ppi }, {
GeoQuadMeshModule | geo_quad_mesh
});
impl_draw!(
self,
module,
widgets,
data,
sender,
{ PPIModule | ppi },
{ GeoQuadMeshModule | geo_quad_mesh } // { EarthModule | earth }
);
}
MonitorInputMsg::KeyPress(key) => {
widgets.renderer.set_key_pressed(key);
@ -261,6 +266,8 @@ impl Component for MonitorModel {
let new_sender = sender.clone();
let mainload = MainLoadAttach::new(None);
// Init
let mut model = MonitorModel {
mainload,
render_cfg,
@ -272,6 +279,7 @@ impl Component for MonitorModel {
};
let widgets = view_output! {};
ComponentParts { model, widgets }
}

View File

@ -1,3 +1,5 @@
use crate::SETTING;
use super::exterior::ExteriorWidget;
use super::interior::InteriorWidget;
use super::{Layer, WindowCoord};
@ -127,12 +129,10 @@ impl ObjectImpl for Render {
}
impl WidgetImpl for Render {
fn realize(&self) {
self.parent_realize();
}
fn unrealize(&self) {
self.obj().make_current();
self.gi.borrow_mut().as_mut().unwrap().destroy();
@ -142,11 +142,9 @@ impl WidgetImpl for Render {
}
impl GLAreaImpl for Render {
fn create_context(&self) -> Option<gtk::gdk::GLContext> {
let context = self.parent_create_context();
context.as_ref().map(|ctx| {
});
context.as_ref().map(|ctx| {});
context
}
@ -195,8 +193,6 @@ impl GLAreaImpl for Render {
);
}
// viewport.unbind();
self.io.borrow_mut().reset();
glib::Propagation::Proceed
@ -213,7 +209,10 @@ impl Render {
widget.make_current();
widget.attach_buffers();
info!("Debug enabled: {}", self.obj().context().unwrap().is_debug_enabled());
info!(
"Debug enabled: {}",
self.obj().context().unwrap().is_debug_enabled()
);
let (mut gi, viewport) = unsafe {
static LOAD_FN: fn(&str) -> *const std::ffi::c_void =
|s| epoxy::get_proc_addr(s) as *const _;
@ -245,7 +244,7 @@ impl Render {
(gi, viewport)
};
gi.prepare();
gi.prepare(&SETTING).expect("Cannot prepare GI");
self.viewport.replace(Some(viewport));
self.gi.replace(Some(gi));
}
@ -317,7 +316,6 @@ fn gl_debug_output(
fn enable_opengl_debugging(ctx: &mut glow::Context) {
unsafe {
if ctx.supported_extensions().contains("GL_ARB_debug_output") {
ctx.enable(glow::DEBUG_OUTPUT);
ctx.enable(glow::DEBUG_OUTPUT_SYNCHRONOUS);
ctx.debug_message_callback(|source, _type, id, severity, message| {
@ -326,7 +324,6 @@ fn enable_opengl_debugging(ctx: &mut glow::Context) {
source, id, severity, message
);
});
} else {
info!("GL_ARB_debug_output not supported");
}

View File

@ -37,15 +37,15 @@ pub struct Setting {
impl Setting {
pub fn new() -> Self {
use std::fs::{read, read_to_string};
use std::fs::read_to_string;
use std::io::*;
let current_dir = env::current_dir().unwrap();
if !current_dir.join("config.toml").exists() {
let default_config = Asset::get("config.toml").unwrap();
let mut folder_path = current_dir.clone();
let mut conf = folder_path.join("config.toml");
let folder_path = current_dir.clone();
let conf = folder_path.join("config.toml");
let mut file = std::fs::File::create_new(&conf).unwrap();
file.write_all(&default_config.data).unwrap();

View File

@ -387,6 +387,14 @@ impl From<LoadedData> for Data {
// }
// }
impl<'a> TryFrom<&'a Data> for &'a () {
type Error = crate::errors::DataError;
fn try_from(value: &'a Data) -> Result<Self, Self::Error> {
Ok(&())
}
}
impl<'a> TryFrom<&'a Data> for &'a Arc<RadarGridData> {
type Error = crate::errors::DataError;

663
test.glsl Normal file
View File

@ -0,0 +1,663 @@
// The MIT License
// Copyright © 2013 Inigo Quilez
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// The license is here only not because I want to (can one
// license pieces of math?), but because people get upset
// if I don't add one...
// A list of useful distance function to simple primitives. All
// these functions (except for ellipsoid) return an exact
// euclidean distance, meaning they produce a better SDF than
// what you'd get if you were constructing them from boolean
// operations (such as cutting an infinite cylinder with two planes).
// List of other 3D SDFs:
// https://www.shadertoy.com/playlist/43cXRl
// and
// https://iquilezles.org/articles/distfunctions
#if HW_PERFORMANCE==0
#define AA 1
#else
#define AA 1 // make this 2 or 3 for antialiasing
#endif
//------------------------------------------------------------------
float dot2( in vec2 v ) { return dot(v,v); }
float dot2( in vec3 v ) { return dot(v,v); }
float ndot( in vec2 a, in vec2 b ) { return a.x*b.x - a.y*b.y; }
float sdPlane( vec3 p )
{
return p.y;
}
float sdSphere( vec3 p, float s )
{
return length(p)-s;
}
float sdBox( vec3 p, vec3 b )
{
vec3 d = abs(p) - b;
return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0));
}
float sdBoxFrame( vec3 p, vec3 b, float e )
{
p = abs(p )-b;
vec3 q = abs(p+e)-e;
return min(min(
length(max(vec3(p.x,q.y,q.z),0.0))+min(max(p.x,max(q.y,q.z)),0.0),
length(max(vec3(q.x,p.y,q.z),0.0))+min(max(q.x,max(p.y,q.z)),0.0)),
length(max(vec3(q.x,q.y,p.z),0.0))+min(max(q.x,max(q.y,p.z)),0.0));
}
float sdEllipsoid( in vec3 p, in vec3 r ) // approximated
{
float k0 = length(p/r);
float k1 = length(p/(r*r));
return k0*(k0-1.0)/k1;
}
float sdTorus( vec3 p, vec2 t )
{
return length( vec2(length(p.xz)-t.x,p.y) )-t.y;
}
float sdCappedTorus(in vec3 p, in vec2 sc, in float ra, in float rb)
{
p.x = abs(p.x);
float k = (sc.y*p.x>sc.x*p.y) ? dot(p.xy,sc) : length(p.xy);
return sqrt( dot(p,p) + ra*ra - 2.0*ra*k ) - rb;
}
float sdHexPrism( vec3 p, vec2 h )
{
vec3 q = abs(p);
const vec3 k = vec3(-0.8660254, 0.5, 0.57735);
p = abs(p);
p.xy -= 2.0*min(dot(k.xy, p.xy), 0.0)*k.xy;
vec2 d = vec2(
length(p.xy - vec2(clamp(p.x, -k.z*h.x, k.z*h.x), h.x))*sign(p.y - h.x),
p.z-h.y );
return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}
float sdOctogonPrism( in vec3 p, in float r, float h )
{
const vec3 k = vec3(-0.9238795325, // sqrt(2+sqrt(2))/2
0.3826834323, // sqrt(2-sqrt(2))/2
0.4142135623 ); // sqrt(2)-1
// reflections
p = abs(p);
p.xy -= 2.0*min(dot(vec2( k.x,k.y),p.xy),0.0)*vec2( k.x,k.y);
p.xy -= 2.0*min(dot(vec2(-k.x,k.y),p.xy),0.0)*vec2(-k.x,k.y);
// polygon side
p.xy -= vec2(clamp(p.x, -k.z*r, k.z*r), r);
vec2 d = vec2( length(p.xy)*sign(p.y), p.z-h );
return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}
float sdCapsule( vec3 p, vec3 a, vec3 b, float r )
{
vec3 pa = p-a, ba = b-a;
float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
return length( pa - ba*h ) - r;
}
float sdRoundCone( in vec3 p, in float r1, float r2, float h )
{
vec2 q = vec2( length(p.xz), p.y );
float b = (r1-r2)/h;
float a = sqrt(1.0-b*b);
float k = dot(q,vec2(-b,a));
if( k < 0.0 ) return length(q) - r1;
if( k > a*h ) return length(q-vec2(0.0,h)) - r2;
return dot(q, vec2(a,b) ) - r1;
}
float sdRoundCone(vec3 p, vec3 a, vec3 b, float r1, float r2)
{
// sampling independent computations (only depend on shape)
vec3 ba = b - a;
float l2 = dot(ba,ba);
float rr = r1 - r2;
float a2 = l2 - rr*rr;
float il2 = 1.0/l2;
// sampling dependant computations
vec3 pa = p - a;
float y = dot(pa,ba);
float z = y - l2;
float x2 = dot2( pa*l2 - ba*y );
float y2 = y*y*l2;
float z2 = z*z*l2;
// single square root!
float k = sign(rr)*rr*rr*x2;
if( sign(z)*a2*z2 > k ) return sqrt(x2 + z2) *il2 - r2;
if( sign(y)*a2*y2 < k ) return sqrt(x2 + y2) *il2 - r1;
return (sqrt(x2*a2*il2)+y*rr)*il2 - r1;
}
float sdTriPrism( vec3 p, vec2 h )
{
const float k = sqrt(3.0);
h.x *= 0.5*k;
p.xy /= h.x;
p.x = abs(p.x) - 1.0;
p.y = p.y + 1.0/k;
if( p.x+k*p.y>0.0 ) p.xy=vec2(p.x-k*p.y,-k*p.x-p.y)/2.0;
p.x -= clamp( p.x, -2.0, 0.0 );
float d1 = length(p.xy)*sign(-p.y)*h.x;
float d2 = abs(p.z)-h.y;
return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.);
}
// vertical
float sdCylinder( vec3 p, vec2 h )
{
vec2 d = abs(vec2(length(p.xz),p.y)) - h;
return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}
// arbitrary orientation
float sdCylinder(vec3 p, vec3 a, vec3 b, float r)
{
vec3 pa = p - a;
vec3 ba = b - a;
float baba = dot(ba,ba);
float paba = dot(pa,ba);
float x = length(pa*baba-ba*paba) - r*baba;
float y = abs(paba-baba*0.5)-baba*0.5;
float x2 = x*x;
float y2 = y*y*baba;
float d = (max(x,y)<0.0)?-min(x2,y2):(((x>0.0)?x2:0.0)+((y>0.0)?y2:0.0));
return sign(d)*sqrt(abs(d))/baba;
}
// vertical
float sdCone( in vec3 p, in vec2 c, float h )
{
vec2 q = h*vec2(c.x,-c.y)/c.y;
vec2 w = vec2( length(p.xz), p.y );
vec2 a = w - q*clamp( dot(w,q)/dot(q,q), 0.0, 1.0 );
vec2 b = w - q*vec2( clamp( w.x/q.x, 0.0, 1.0 ), 1.0 );
float k = sign( q.y );
float d = min(dot( a, a ),dot(b, b));
float s = max( k*(w.x*q.y-w.y*q.x),k*(w.y-q.y) );
return sqrt(d)*sign(s);
}
float sdCappedCone( in vec3 p, in float h, in float r1, in float r2 )
{
vec2 q = vec2( length(p.xz), p.y );
vec2 k1 = vec2(r2,h);
vec2 k2 = vec2(r2-r1,2.0*h);
vec2 ca = vec2(q.x-min(q.x,(q.y < 0.0)?r1:r2), abs(q.y)-h);
vec2 cb = q - k1 + k2*clamp( dot(k1-q,k2)/dot2(k2), 0.0, 1.0 );
float s = (cb.x < 0.0 && ca.y < 0.0) ? -1.0 : 1.0;
return s*sqrt( min(dot2(ca),dot2(cb)) );
}
float sdCappedCone(vec3 p, vec3 a, vec3 b, float ra, float rb)
{
float rba = rb-ra;
float baba = dot(b-a,b-a);
float papa = dot(p-a,p-a);
float paba = dot(p-a,b-a)/baba;
float x = sqrt( papa - paba*paba*baba );
float cax = max(0.0,x-((paba<0.5)?ra:rb));
float cay = abs(paba-0.5)-0.5;
float k = rba*rba + baba;
float f = clamp( (rba*(x-ra)+paba*baba)/k, 0.0, 1.0 );
float cbx = x-ra - f*rba;
float cby = paba - f;
float s = (cbx < 0.0 && cay < 0.0) ? -1.0 : 1.0;
return s*sqrt( min(cax*cax + cay*cay*baba,
cbx*cbx + cby*cby*baba) );
}
// c is the sin/cos of the desired cone angle
float sdSolidAngle(vec3 pos, vec2 c, float ra)
{
vec2 p = vec2( length(pos.xz), pos.y );
float l = length(p) - ra;
float m = length(p - c*clamp(dot(p,c),0.0,ra) );
return max(l,m*sign(c.y*p.x-c.x*p.y));
}
float sdOctahedron(vec3 p, float s)
{
p = abs(p);
float m = p.x + p.y + p.z - s;
// exact distance
#if 0
vec3 o = min(3.0*p - m, 0.0);
o = max(6.0*p - m*2.0 - o*3.0 + (o.x+o.y+o.z), 0.0);
return length(p - s*o/(o.x+o.y+o.z));
#endif
// exact distance
#if 1
vec3 q;
if( 3.0*p.x < m ) q = p.xyz;
else if( 3.0*p.y < m ) q = p.yzx;
else if( 3.0*p.z < m ) q = p.zxy;
else return m*0.57735027;
float k = clamp(0.5*(q.z-q.y+s),0.0,s);
return length(vec3(q.x,q.y-s+k,q.z-k));
#endif
// bound, not exact
#if 0
return m*0.57735027;
#endif
}
float sdPyramid( in vec3 p, in float h )
{
float m2 = h*h + 0.25;
// symmetry
p.xz = abs(p.xz);
p.xz = (p.z>p.x) ? p.zx : p.xz;
p.xz -= 0.5;
// project into face plane (2D)
vec3 q = vec3( p.z, h*p.y - 0.5*p.x, h*p.x + 0.5*p.y);
float s = max(-q.x,0.0);
float t = clamp( (q.y-0.5*p.z)/(m2+0.25), 0.0, 1.0 );
float a = m2*(q.x+s)*(q.x+s) + q.y*q.y;
float b = m2*(q.x+0.5*t)*(q.x+0.5*t) + (q.y-m2*t)*(q.y-m2*t);
float d2 = min(q.y,-q.x*m2-q.y*0.5) > 0.0 ? 0.0 : min(a,b);
// recover 3D and scale, and add sign
return sqrt( (d2+q.z*q.z)/m2 ) * sign(max(q.z,-p.y));;
}
// la,lb=semi axis, h=height, ra=corner
float sdRhombus(vec3 p, float la, float lb, float h, float ra)
{
p = abs(p);
vec2 b = vec2(la,lb);
float f = clamp( (ndot(b,b-2.0*p.xz))/dot(b,b), -1.0, 1.0 );
vec2 q = vec2(length(p.xz-0.5*b*vec2(1.0-f,1.0+f))*sign(p.x*b.y+p.z*b.x-b.x*b.y)-ra, p.y-h);
return min(max(q.x,q.y),0.0) + length(max(q,0.0));
}
float sdHorseshoe( in vec3 p, in vec2 c, in float r, in float le, vec2 w )
{
p.x = abs(p.x);
float l = length(p.xy);
p.xy = mat2(-c.x, c.y,
c.y, c.x)*p.xy;
p.xy = vec2((p.y>0.0 || p.x>0.0)?p.x:l*sign(-c.x),
(p.x>0.0)?p.y:l );
p.xy = vec2(p.x,abs(p.y-r))-vec2(le,0.0);
vec2 q = vec2(length(max(p.xy,0.0)) + min(0.0,max(p.x,p.y)),p.z);
vec2 d = abs(q) - w;
return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}
float sdU( in vec3 p, in float r, in float le, vec2 w )
{
p.x = (p.y>0.0) ? abs(p.x) : length(p.xy);
p.x = abs(p.x-r);
p.y = p.y - le;
float k = max(p.x,p.y);
vec2 q = vec2( (k<0.0) ? -k : length(max(p.xy,0.0)), abs(p.z) ) - w;
return length(max(q,0.0)) + min(max(q.x,q.y),0.0);
}
//------------------------------------------------------------------
vec2 opU( vec2 d1, vec2 d2 )
{
return (d1.x<d2.x) ? d1 : d2;
}
//------------------------------------------------------------------
#define ZERO (min(iFrame,0))
//------------------------------------------------------------------
vec2 map( in vec3 pos )
{
vec2 res = vec2( pos.y, 0.0 );
// bounding box
if( sdBox( pos-vec3(-2.0,0.3,0.25),vec3(0.3,0.3,1.0) )<res.x )
{
res = opU( res, vec2( sdSphere( pos-vec3(-2.0,0.25, 0.0), 0.25 ), 26.9 ) );
res = opU( res, vec2( sdRhombus( (pos-vec3(-2.0,0.25, 1.0)).xzy, 0.15, 0.25, 0.04, 0.08 ),17.0 ) );
}
// bounding box
if( sdBox( pos-vec3(0.0,0.3,-1.0),vec3(0.35,0.3,2.5) )<res.x )
{
res = opU( res, vec2( sdCappedTorus((pos-vec3( 0.0,0.30, 1.0))*vec3(1,-1,1), vec2(0.866025,-0.5), 0.25, 0.05), 25.0) );
res = opU( res, vec2( sdBoxFrame( pos-vec3( 0.0,0.25, 0.0), vec3(0.3,0.25,0.2), 0.025 ), 16.9 ) );
res = opU( res, vec2( sdCone( pos-vec3( 0.0,0.45,-1.0), vec2(0.6,0.8),0.45 ), 55.0 ) );
res = opU( res, vec2( sdCappedCone( pos-vec3( 0.0,0.25,-2.0), 0.25, 0.25, 0.1 ), 13.67 ) );
res = opU( res, vec2( sdSolidAngle( pos-vec3( 0.0,0.00,-3.0), vec2(3,4)/5.0, 0.4 ), 49.13 ) );
}
// bounding box
if( sdBox( pos-vec3(1.0,0.3,-1.0),vec3(0.35,0.3,2.5) )<res.x )
{
res = opU( res, vec2( sdTorus( (pos-vec3( 1.0,0.30, 1.0)).xzy, vec2(0.25,0.05) ), 7.1 ) );
res = opU( res, vec2( sdBox( pos-vec3( 1.0,0.25, 0.0), vec3(0.3,0.25,0.1) ), 3.0 ) );
res = opU( res, vec2( sdCapsule( pos-vec3( 1.0,0.00,-1.0),vec3(-0.1,0.1,-0.1), vec3(0.2,0.4,0.2), 0.1 ), 31.9 ) );
res = opU( res, vec2( sdCylinder( pos-vec3( 1.0,0.25,-2.0), vec2(0.15,0.25) ), 8.0 ) );
res = opU( res, vec2( sdHexPrism( pos-vec3( 1.0,0.2,-3.0), vec2(0.2,0.05) ), 18.4 ) );
}
// bounding box
if( sdBox( pos-vec3(-1.0,0.35,-1.0),vec3(0.35,0.35,2.5))<res.x )
{
res = opU( res, vec2( sdPyramid( pos-vec3(-1.0,-0.6,-3.0), 1.0 ), 13.56 ) );
res = opU( res, vec2( sdOctahedron( pos-vec3(-1.0,0.15,-2.0), 0.35 ), 23.56 ) );
res = opU( res, vec2( sdTriPrism( pos-vec3(-1.0,0.15,-1.0), vec2(0.3,0.05) ),43.5 ) );
res = opU( res, vec2( sdEllipsoid( pos-vec3(-1.0,0.25, 0.0), vec3(0.2, 0.25, 0.05) ), 43.17 ) );
res = opU( res, vec2( sdHorseshoe( pos-vec3(-1.0,0.25, 1.0), vec2(cos(1.3),sin(1.3)), 0.2, 0.3, vec2(0.03,0.08) ), 11.5 ) );
}
// bounding box
if( sdBox( pos-vec3(2.0,0.3,-1.0),vec3(0.35,0.3,2.5) )<res.x )
{
res = opU( res, vec2( sdOctogonPrism(pos-vec3( 2.0,0.2,-3.0), 0.2, 0.05), 51.8 ) );
res = opU( res, vec2( sdCylinder( pos-vec3( 2.0,0.14,-2.0), vec3(0.1,-0.1,0.0), vec3(-0.2,0.35,0.1), 0.08), 31.2 ) );
res = opU( res, vec2( sdCappedCone( pos-vec3( 2.0,0.09,-1.0), vec3(0.1,0.0,0.0), vec3(-0.2,0.40,0.1), 0.15, 0.05), 46.1 ) );
res = opU( res, vec2( sdRoundCone( pos-vec3( 2.0,0.15, 0.0), vec3(0.1,0.0,0.0), vec3(-0.1,0.35,0.1), 0.15, 0.05), 51.7 ) );
res = opU( res, vec2( sdRoundCone( pos-vec3( 2.0,0.20, 1.0), 0.2, 0.1, 0.3 ), 37.0 ) );
}
return res;
}
// https://iquilezles.org/articles/boxfunctions
vec2 iBox( in vec3 ro, in vec3 rd, in vec3 rad )
{
vec3 m = 1.0/rd;
vec3 n = m*ro;
vec3 k = abs(m)*rad;
vec3 t1 = -n - k;
vec3 t2 = -n + k;
return vec2( max( max( t1.x, t1.y ), t1.z ),
min( min( t2.x, t2.y ), t2.z ) );
}
vec2 raycast( in vec3 ro, in vec3 rd )
{
vec2 res = vec2(-1.0,-1.0);
float tmin = 1.0;
float tmax = 20.0;
// raytrace floor plane
float tp1 = (0.0-ro.y)/rd.y;
if( tp1>0.0 )
{
tmax = min( tmax, tp1 );
res = vec2( tp1, 1.0 );
}
//else return res;
// raymarch primitives
vec2 tb = iBox( ro-vec3(0.0,0.4,-0.5), rd, vec3(2.5,0.41,3.0) );
if( tb.x<tb.y && tb.y>0.0 && tb.x<tmax)
{
//return vec2(tb.x,2.0);
tmin = max(tb.x,tmin);
tmax = min(tb.y,tmax);
float t = tmin;
for( int i=0; i<70 && t<tmax; i++ )
{
vec2 h = map( ro+rd*t );
if( abs(h.x)<(0.0001*t) )
{
res = vec2(t,h.y);
break;
}
t += h.x;
}
}
return res;
}
// https://iquilezles.org/articles/rmshadows
float calcSoftshadow( in vec3 ro, in vec3 rd, in float mint, in float tmax )
{
// bounding volume
float tp = (0.8-ro.y)/rd.y; if( tp>0.0 ) tmax = min( tmax, tp );
float res = 1.0;
float t = mint;
for( int i=ZERO; i<24; i++ )
{
float h = map( ro + rd*t ).x;
float s = clamp(8.0*h/t,0.0,1.0);
res = min( res, s );
t += clamp( h, 0.01, 0.2 );
if( res<0.004 || t>tmax ) break;
}
res = clamp( res, 0.0, 1.0 );
return res*res*(3.0-2.0*res);
}
// https://iquilezles.org/articles/normalsSDF
vec3 calcNormal( in vec3 pos )
{
#if 0
vec2 e = vec2(1.0,-1.0)*0.5773*0.0005;
return normalize( e.xyy*map( pos + e.xyy ).x +
e.yyx*map( pos + e.yyx ).x +
e.yxy*map( pos + e.yxy ).x +
e.xxx*map( pos + e.xxx ).x );
#else
// inspired by tdhooper and klems - a way to prevent the compiler from inlining map() 4 times
vec3 n = vec3(0.0);
for( int i=ZERO; i<4; i++ )
{
vec3 e = 0.5773*(2.0*vec3((((i+3)>>1)&1),((i>>1)&1),(i&1))-1.0);
n += e*map(pos+0.0005*e).x;
//if( n.x+n.y+n.z>100.0 ) break;
}
return normalize(n);
#endif
}
// https://iquilezles.org/articles/nvscene2008/rwwtt.pdf
float calcAO( in vec3 pos, in vec3 nor )
{
float occ = 0.0;
float sca = 1.0;
for( int i=ZERO; i<5; i++ )
{
float h = 0.01 + 0.12*float(i)/4.0;
float d = map( pos + h*nor ).x;
occ += (h-d)*sca;
sca *= 0.95;
if( occ>0.35 ) break;
}
return clamp( 1.0 - 3.0*occ, 0.0, 1.0 ) * (0.5+0.5*nor.y);
}
// https://iquilezles.org/articles/checkerfiltering
float checkersGradBox( in vec2 p, in vec2 dpdx, in vec2 dpdy )
{
// filter kernel
vec2 w = abs(dpdx)+abs(dpdy) + 0.001;
// analytical integral (box filter)
vec2 i = 2.0*(abs(fract((p-0.5*w)*0.5)-0.5)-abs(fract((p+0.5*w)*0.5)-0.5))/w;
// xor pattern
return 0.5 - 0.5*i.x*i.y;
}
vec3 render( in vec3 ro, in vec3 rd, in vec3 rdx, in vec3 rdy )
{
// background
vec3 col = vec3(0.7, 0.7, 0.9) - max(rd.y,0.0)*0.3;
// raycast scene
vec2 res = raycast(ro,rd);
float t = res.x;
float m = res.y;
if( m>-0.5 )
{
vec3 pos = ro + t*rd;
vec3 nor = (m<1.5) ? vec3(0.0,1.0,0.0) : calcNormal( pos );
vec3 ref = reflect( rd, nor );
// material
col = 0.2 + 0.2*sin( m*2.0 + vec3(0.0,1.0,2.0) );
float ks = 1.0;
if( m<1.5 )
{
// project pixel footprint into the plane
vec3 dpdx = ro.y*(rd/rd.y-rdx/rdx.y);
vec3 dpdy = ro.y*(rd/rd.y-rdy/rdy.y);
float f = checkersGradBox( 3.0*pos.xz, 3.0*dpdx.xz, 3.0*dpdy.xz );
col = 0.15 + f*vec3(0.05);
ks = 0.4;
}
// lighting
float occ = calcAO( pos, nor );
vec3 lin = vec3(0.0);
// sun
{
vec3 lig = normalize( vec3(-0.5, 0.4, -0.6) );
vec3 hal = normalize( lig-rd );
float dif = clamp( dot( nor, lig ), 0.0, 1.0 );
//if( dif>0.0001 )
dif *= calcSoftshadow( pos, lig, 0.02, 2.5 );
float spe = pow( clamp( dot( nor, hal ), 0.0, 1.0 ),16.0);
spe *= dif;
spe *= 0.04+0.96*pow(clamp(1.0-dot(hal,lig),0.0,1.0),5.0);
//spe *= 0.04+0.96*pow(clamp(1.0-sqrt(0.5*(1.0-dot(rd,lig))),0.0,1.0),5.0);
lin += col*2.20*dif*vec3(1.30,1.00,0.70);
lin += 5.00*spe*vec3(1.30,1.00,0.70)*ks;
}
// sky
{
float dif = sqrt(clamp( 0.5+0.5*nor.y, 0.0, 1.0 ));
dif *= occ;
float spe = smoothstep( -0.2, 0.2, ref.y );
spe *= dif;
spe *= 0.04+0.96*pow(clamp(1.0+dot(nor,rd),0.0,1.0), 5.0 );
//if( spe>0.001 )
spe *= calcSoftshadow( pos, ref, 0.02, 2.5 );
lin += col*0.60*dif*vec3(0.40,0.60,1.15);
lin += 2.00*spe*vec3(0.40,0.60,1.30)*ks;
}
// back
{
float dif = clamp( dot( nor, normalize(vec3(0.5,0.0,0.6))), 0.0, 1.0 )*clamp( 1.0-pos.y,0.0,1.0);
dif *= occ;
lin += col*0.55*dif*vec3(0.25,0.25,0.25);
}
// sss
{
float dif = pow(clamp(1.0+dot(nor,rd),0.0,1.0),2.0);
dif *= occ;
lin += col*0.25*dif*vec3(1.00,1.00,1.00);
}
col = lin;
col = mix( col, vec3(0.7,0.7,0.9), 1.0-exp( -0.0001*t*t*t ) );
}
return vec3( clamp(col,0.0,1.0) );
}
mat3 setCamera( in vec3 ro, in vec3 ta, float cr )
{
vec3 cw = normalize(ta-ro);
vec3 cp = vec3(sin(cr), cos(cr),0.0);
vec3 cu = normalize( cross(cw,cp) );
vec3 cv = ( cross(cu,cw) );
return mat3( cu, cv, cw );
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 mo = iMouse.xy/iResolution.xy;
float time = 32.0 + iTime*1.5;
// camera
vec3 ta = vec3( 0.25, -0.75, -0.75 );
vec3 ro = ta + vec3( 4.5*cos(0.1*time + 7.0*mo.x), 2.2, 4.5*sin(0.1*time + 7.0*mo.x) );
// camera-to-world transformation
mat3 ca = setCamera( ro, ta, 0.0 );
vec3 tot = vec3(0.0);
#if AA>1
for( int m=ZERO; m<AA; m++ )
for( int n=ZERO; n<AA; n++ )
{
// pixel coordinates
vec2 o = vec2(float(m),float(n)) / float(AA) - 0.5;
vec2 p = (2.0*(fragCoord+o)-iResolution.xy)/iResolution.y;
#else
vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y;
#endif
// focal length
const float fl = 2.5;
// ray direction
vec3 rd = ca * normalize( vec3(p,fl) );
// ray differentials
vec2 px = (2.0*(fragCoord+vec2(1.0,0.0))-iResolution.xy)/iResolution.y;
vec2 py = (2.0*(fragCoord+vec2(0.0,1.0))-iResolution.xy)/iResolution.y;
vec3 rdx = ca * normalize( vec3(px,fl) );
vec3 rdy = ca * normalize( vec3(py,fl) );
// render
vec3 col = render( ro, rd, rdx, rdy );
// gain
// col = col*3.0/(2.5+col);
// gamma
col = pow( col, vec3(0.4545) );
tot += col;
#if AA>1
}
tot /= float(AA*AA);
#endif
fragColor = vec4( tot, 1.0 );
}