Skip to main content

cube_shaded/
cube_shaded.rs

1use 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
14// This example uses static draw calls into rusterix, bypassing the game engine API.
15pub 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    /// Draw a cube and a rectangle
120    fn draw(&mut self, pixels: &mut [u8], ctx: &mut TheContext) {
121        let _start = get_time();
122
123        // Animate light in circle around Y-axis
124        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        // Set it up
128        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,     // Destination buffer
139            ctx.width,  // Destination buffer width
140            ctx.height, // Destination buffer height
141            80,         // Tile size
142            &self.assets,
143        );
144
145        let _stop = get_time();
146        println!("Execution time: {:?} ms.", _stop - _start);
147    }
148
149    // Hover event
150    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    // Query if the widget needs a redraw, we redraw at max speed (which is not necessary)
159    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}