extern crate spottedcat as sc;
use sc::{
Context, DrawOption, Image, ImageShaderBindings, ImageShaderBlendMode, ImageShaderTemplate, Pt,
ShaderOpts, Spot, WindowConfig, register_image_shader_template,
};
struct ImageShaderTemplateExample {
sprite: Image,
noise: Image,
shader_id: u32,
time: f32,
}
impl Spot for ImageShaderTemplateExample {
fn initialize(ctx: &mut Context) -> Self {
let sprite = Image::new(ctx, Pt::from(96.0), Pt::from(96.0), &build_sprite_rgba()).unwrap();
let noise = Image::new(ctx, Pt::from(64.0), Pt::from(64.0), &build_noise_rgba()).unwrap();
let shader_id = register_image_shader_template(
ctx,
ImageShaderTemplate::new()
.with_extra_textures(true)
.with_blend_mode(ImageShaderBlendMode::Add)
.with_history_at(0)
.with_texture_alias(1, "t_noise")
.with_screen_at(2)
.with_shared(
r#"
fn shimmer_tint(color: vec3<f32>, tint: vec3<f32>, noise: vec3<f32>, pulse: f32) -> vec3<f32> {
return color * tint * (0.65 + noise * 0.8) * pulse;
}
"#,
)
.with_vertex_body("out.local_uv = out.local_uv * 0.96 + vec2<f32>(0.02, 0.02);")
.with_fragment_body(
r#"
let history = textureSample(t_history, extra_samp, in.uv);
let noise = textureSample(t_noise, extra_samp, fract(in.local_uv * 3.0 + vec2<f32>(user_globals[0].x * 0.13, user_globals[0].x * 0.09))).rgb;
let screen_sample = textureSample(t_screen, extra_samp, in.uv);
let tint = user_globals[1].rgb;
let pulse = 0.55 + 0.45 * sin(user_globals[0].x * 2.2 + in.local_uv.x * 6.2831);
let trail = history.rgb * 0.94;
let composed = max(trail * 0.98 + screen_sample.rgb * 0.08, shimmer_tint(src.rgb, tint, noise, pulse));
let alpha = max(src.a, history.a * 0.96) * opacity;
return vec4<f32>(composed, alpha);
"#,
),
);
Self {
sprite,
noise,
shader_id,
time: 0.0,
}
}
fn update(&mut self, _ctx: &mut Context, dt: std::time::Duration) {
self.time += dt.as_secs_f32();
}
fn draw(&mut self, ctx: &mut Context, screen: Image) {
let (w, h) = spottedcat::window_size(ctx);
let x = w.as_f32() * 0.5 + self.time.cos() * 180.0;
let y = h.as_f32() * 0.5 + (self.time * 1.7).sin() * 120.0;
let mut shader_opts = ShaderOpts::default();
shader_opts.set_vec4(0, [self.time, 0.0, 0.0, 0.0]);
shader_opts.set_vec4(1, [1.0, 0.45, 0.9, 1.0]);
let bindings = ImageShaderBindings::new()
.with_history()
.with_image("t_noise", self.noise)
.with_screen();
screen.draw_with_shader_bindings(
ctx,
self.sprite,
self.shader_id,
DrawOption::default()
.with_position([Pt::from(x), Pt::from(y)])
.with_scale([2.0, 2.0]),
shader_opts,
bindings,
);
}
fn remove(&mut self, _ctx: &mut Context) {}
}
fn build_sprite_rgba() -> Vec<u8> {
let size = 96u32;
let mut out = vec![0u8; (size * size * 4) as usize];
for y in 0..size {
for x in 0..size {
let dx = x as f32 - size as f32 * 0.5;
let dy = y as f32 - size as f32 * 0.5;
let dist = (dx * dx + dy * dy).sqrt() / (size as f32 * 0.5);
let ring = (1.0 - dist).clamp(0.0, 1.0);
let alpha = if dist < 1.0 {
(ring * ring * 255.0) as u8
} else {
0
};
let offset = ((y * size + x) * 4) as usize;
out[offset] = (255.0 * ring) as u8;
out[offset + 1] = (180.0 * ring) as u8;
out[offset + 2] = 255;
out[offset + 3] = alpha;
}
}
out
}
fn build_noise_rgba() -> Vec<u8> {
let size = 64u32;
let mut out = vec![0u8; (size * size * 4) as usize];
for y in 0..size {
for x in 0..size {
let v = (((x * 37 + y * 57 + x * y * 13) % 255) as u8).max(32);
let offset = ((y * size + x) * 4) as usize;
out[offset] = v;
out[offset + 1] = v.saturating_sub(20);
out[offset + 2] = 255u8.saturating_sub(v / 3);
out[offset + 3] = 255;
}
}
out
}
fn main() {
spottedcat::run::<ImageShaderTemplateExample>(WindowConfig {
title: "Image Shader Template".to_string(),
width: Pt::from(960.0),
height: Pt::from(640.0),
..Default::default()
});
}