black_hole/
black_hole.rs

1use hoplite::{AppConfig, Color, OrbitCamera, OrbitMode, Quat, Transform, Vec3, run_with_config};
2use winit::event::MouseButton;
3
4fn main() {
5    run_with_config(AppConfig::new().title("Black Hole"), |ctx| {
6        // Setup: configure render pipeline and load assets
7        ctx.default_font(16.0);
8
9        // Hot-reloadable shaders - edit the files and see changes live!
10        ctx.hot_effect_world("examples/shaders/black_hole.wgsl");
11
12        // Enable 3D mesh rendering (after background, before post-process)
13        ctx.enable_mesh_rendering();
14
15        // Add post-processing after mesh rendering
16        ctx.hot_post_process_world("examples/shaders/gravitational_lensing.wgsl");
17
18        // Create a cube mesh
19        let cube = ctx.mesh_cube();
20
21        // Create a procedural blocky noise texture (16x16 for that pixelated look)
22        let texture = ctx.texture_blocky_noise(16, 42);
23
24        // Create a 2D sprite for the UI (same procedural texture, rendered in 2D layer)
25        let sprite = ctx.sprite_blocky_noise(32, 123);
26
27        // Camera: auto-rotate or interactive orbit
28        let mut orbit = OrbitCamera::new()
29            .target(Vec3::ZERO)
30            .distance(32.0)
31            .elevation(0.3)
32            .fov(80.0)
33            .mode(OrbitMode::Interactive);
34
35        // State for toggling cube visibility
36        let mut cube_visible = true;
37
38        // Frame loop
39        move |frame| {
40            orbit.update(frame.input, frame.dt);
41            frame.set_camera(orbit.camera());
42
43            // Animate the cubes: hover above the black hole and rotate
44            let hover_height = 8.0 + (frame.time * 0.5).sin() * 0.5;
45
46            // Draw a 3x3 grid of textured cubes (if visible)
47            if cube_visible {
48                let spacing = 12.0;
49                for row in 0..3 {
50                    for col in 0..3 {
51                        let idx = row * 3 + col;
52                        // Offset rotation per cube for visual variety
53                        let phase = idx as f32 * 0.3;
54                        let rotation = Quat::from_euler(
55                            glam::EulerRot::YXZ,
56                            frame.time * 0.7 + phase,
57                            frame.time * 0.5 + phase,
58                            frame.time * 0.3 + phase,
59                        );
60
61                        let x = (col as f32 - 1.0) * spacing;
62                        let z = (row as f32 - 1.0) * spacing;
63
64                        frame
65                            .mesh(cube)
66                            .transform(
67                                Transform::new()
68                                    .position(Vec3::new(x, hover_height, z))
69                                    .rotation(rotation)
70                                    .uniform_scale(3.0),
71                            )
72                            .texture(texture)
73                            .draw();
74                    }
75                }
76            }
77
78            // Draw debug overlay
79            let y = frame.panel_titled(10.0, 10.0, 300.0, 120.0, "Debug Overlay");
80            frame.text(18.0, y + 8.0, &format!("FPS: {:.1}", frame.fps()));
81            frame.text_color(
82                18.0,
83                y + 28.0,
84                &format!("Time: {:.1}s", frame.time),
85                Color::rgba(0.7, 0.7, 0.7, 1.0),
86            );
87
88            // Draw the 2D sprite in the bottom-right corner (clickable toggle for cube visibility)
89            let sprite_x = frame.width() as f32 - 80.0;
90            let sprite_y = frame.height() as f32 - 80.0;
91            let sprite_w = 64.0;
92            let sprite_h = 64.0;
93
94            // Check for click on sprite
95            if frame.input.mouse_pressed(MouseButton::Left) {
96                let mouse_pos = frame.input.mouse_position();
97                if mouse_pos.x >= sprite_x
98                    && mouse_pos.x <= sprite_x + sprite_w
99                    && mouse_pos.y >= sprite_y
100                    && mouse_pos.y <= sprite_y + sprite_h
101                {
102                    cube_visible = !cube_visible;
103                }
104            }
105
106            // Draw sprite with tint based on cube visibility
107            let tint = if cube_visible {
108                Color::WHITE
109            } else {
110                Color::rgba(0.4, 0.4, 0.4, 1.0) // Grayed out when cube is hidden
111            };
112            frame.sprite_scaled_tinted(sprite, sprite_x, sprite_y, sprite_w, sprite_h, tint);
113
114            // Draw label above the sprite
115            let label = if cube_visible {
116                "Cubes: Shown"
117            } else {
118                "Cubes: Hidden"
119            };
120            frame.text(sprite_x - 40.0, sprite_y - 20.0, label);
121        }
122    });
123}