cube_shaded/
cube_shaded.rs1use rusterix::prelude::*;
2use std::path::Path;
3use std::time::Instant;
4use theframework::*;
5use vek::{Vec2, Vec3, Vec4};
6
7fn main() {
8 let cube = Cube::new();
9 let app = TheApp::new();
10
11 () = app.run(Box::new(cube));
12}
13
14pub struct Cube {
16 camera: D3OrbitCamera,
17 scene: Scene,
18 assets: Assets,
19 start_time: Instant,
20}
21
22impl TheTrait for Cube {
23 fn new() -> Self
24 where
25 Self: Sized,
26 {
27 let mut scene = Scene::from_static(
28 vec![Batch2D::from_rectangle(0.0, 0.0, 200.0, 200.0)],
29 vec![
30 Batch3D::from_box(-0.5, -0.5, -0.5, 1.0, 1.0, 1.0)
31 .source(PixelSource::StaticTileIndex(0))
32 .cull_mode(CullMode::Off)
33 .ambient_color(Vec3::broadcast(0.3))
34 .shader(0)
35 .with_computed_normals(),
36 ],
37 )
38 .lights(vec![
39 Light::new(LightType::Point)
40 .with_intensity(1.0)
41 .with_color([1.0, 1.0, 0.95])
42 .compile(),
43 ])
44 .background(Box::new(VGrayGradientShader::new()));
45
46 scene.add_shader(
47 r#"
48 fn shade() {
49 // Procedural wood: concentric growth rings warped by turbulence + fine grain.
50 // Only the .x channel of textures is used (value channel).
51 let t = time * 0.0;
52
53 // Move and scale domain; center the rings roughly in the middle of each face.
54 let uv2 = uv / 3.0 - vec2(1.5);
55
56 // fBm turbulence (zero-mean) to warp the rings
57 let n1 = sample(uv2 + vec2(t, 0.0), "fbm_perlin"); // [0,1]
58 let n2 = sample(uv2 * 2.0 + vec2(0.0, t*0.7), "fbm_perlin");
59 let turb = 0.65 * n1 + 0.35 * n2; // [0,1]
60 let turb_zm = (turb - 0.5) * 2.0; // [-1,1]
61
62 // Radial distance from center (log cross-section look)
63 let r = length(uv2);
64
65 // Warp rings by turbulence (phase modulation)
66 let ring_freq = 10.0; // number of rings
67 let ring_warp = 0.22; // strength of warp
68 let rings = r + ring_warp * turb_zm;
69 let waves = sin(rings * ring_freq);
70
71 // Map sine to ring mask; sharpen valleys to make rings thinner
72 let rings_mask = pow(1.0 - abs(waves), 3.0);
73
74 // Fine longitudinal grain: high-frequency value noise stretched along X
75 let grain_uv = vec2(uv2.x * 8.0, uv2.y * 40.0);
76 let g = sample(grain_uv + vec2(0.0, t*0.5), "value");
77 let grain = (g - 0.5) * 2.0; // zero-mean
78
79 // Base wood hues
80 let base_light = vec3(0.72, 0.52, 0.32);
81 let base_dark = vec3(0.45, 0.30, 0.16);
82
83 // Mix light/dark by ring mask
84 color = mix(base_light, base_dark, rings_mask);
85
86 // Apply subtle anisotropic grain as a multiplicative zero-mean factor
87 color *= (1.0 + 0.06 * grain);
88
89 // Optional pore streaks (cathedrals): directional bands along Y with slight turbulence
90 let band = uv2.y + 0.15 * turb_zm;
91 let cathedral = pow(1.0 - abs(sin(band * 6.0)), 4.0);
92 color = mix(color, color * 0.9, cathedral * 0.2);
93
94 // Roughness varies: pores are rougher, rings smoother
95 roughness = 0.6 + cathedral * 0.3;
96
97 // 16 Colors
98 //let color_steps = 16.0;
99 //color = floor(color * color_steps) / color_steps;
100 }
101 "#,
102 );
103
104 let assets = Assets::default().textures(vec![Tile::from_texture(Texture::from_image(
105 Path::new("images/logo.png"),
106 ))]);
107
108 let mut camera = D3OrbitCamera::new();
109 camera.set_parameter_f32("distance", 1.5);
110
111 Self {
112 camera,
113 scene,
114 start_time: Instant::now(),
115 assets,
116 }
117 }
118
119 fn draw(&mut self, pixels: &mut [u8], ctx: &mut TheContext) {
121 let _start = get_time();
122
123 let elapsed = self.start_time.elapsed().as_secs_f32() * 1.5;
125 self.scene.lights[0].position = Vec3::new(2.0 * elapsed.cos(), 0.8, 2.0 * elapsed.sin());
126
127 Rasterizer::setup(
129 None,
130 self.camera.view_matrix(),
131 self.camera
132 .projection_matrix(ctx.width as f32, ctx.height as f32),
133 )
134 .ambient(Vec4::broadcast(0.1))
135 .time(elapsed)
136 .rasterize(
137 &mut self.scene,
138 pixels, ctx.width, ctx.height, 80, &self.assets,
143 );
144
145 let _stop = get_time();
146 println!("Execution time: {:?} ms.", _stop - _start);
147 }
148
149 fn hover(&mut self, x: f32, y: f32, ctx: &mut TheContext) -> bool {
151 self.camera.set_parameter_vec2(
152 "from_normalized",
153 Vec2::new(x / ctx.width as f32, y / ctx.height as f32),
154 );
155 true
156 }
157
158 fn update(&mut self, _ctx: &mut TheContext) -> bool {
160 true
161 }
162
163 fn window_title(&self) -> String {
164 "Rusterix Cube Demo".to_string()
165 }
166}
167
168fn get_time() -> u128 {
169 #[cfg(target_arch = "wasm32")]
170 {
171 web_sys::window().unwrap().performance().unwrap().now() as u128
172 }
173 #[cfg(not(target_arch = "wasm32"))]
174 {
175 let stop = std::time::SystemTime::now()
176 .duration_since(std::time::UNIX_EPOCH)
177 .expect("Time went backwards");
178 stop.as_millis()
179 }
180}